<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                上個小節中我們共同學習了如何自定義驗證器,我們把上節中那種可以馬上得到驗證結果的驗證器稱為同步驗證器。有些時候,我們在進行數據驗證時,還需要去請求后臺相關的API,比如后臺不允許兩個學生使用相同的學號,這時候驗證器則需要后臺的協助。我們把這種需要后臺協助的驗證器稱為異步驗證器。 ## Api 當前后臺提供了一個校驗學號是否可用的方法,其API如下: ```bash GET /student/numberIsExist ``` | **類型Type** | **名稱Name** | **描述Description** | 必填 | **類型Schema** | 默認值 | | :------------ | :----------- | :------------------ | ---- | :---------------------------------------------------- | ------ | | Param請求參數 | `number` | 學號 | 是 | `string` | | | Response響應 | | Status Code: 200 | | 學號已存在,則返回`true`;學號不存在,則返回`false`。 | | ### MockApi 依據API我們建立MockApi,模擬實現如下邏輯,傳入的學號為`032282`時,則返回`true`,表示該學號已存在;傳入其它的學號時,返回`false`,表示該學號尚不存在。 我們來到`mock-api`文件夾,新建`student.mock.api.ts`: ```bash panjiedeMacBook-Pro:mock-api panjie$ pwd /Users/panjie/github/mengyunzhi/angular11-guild/first-app/src/app/mock-api panjiedeMacBook-Pro:mock-api panjie$ touch student.mock.api.ts panjiedeMacBook-Pro:mock-api panjie$ ls clazz.mock.api.ts student.mock.api.ts mock-api-testing.module.ts teacher.mock.api.ts ``` 然后初始化如下: ```typescript import {ApiInjector, MockApiInterface} from '@yunzhi/ng-mock-api'; /** * 學生模擬API. */ export class StudentMockApi implements MockApiInterface { getInjectors(): ApiInjector<any>[] { return []; } } ``` 最后加入模擬信息: ```typescript export class StudentMockApi implements MockApiInterface { getInjectors(): ApiInjector<any>[] { return [{ method: 'GET', url: '/student/numberIsExist', result: (urlMatches: any, options: RequestOptions): boolean => { const params = options.params as HttpParams; if (!params.has('number')) { throw new Error('未接收到查詢參數number'); } const stuNumber = params.get('number') as string; if (stuNumber === '032282') { return true; } else { return false; } } }]; } ``` 此時一個MockApi便建立完成了。若想使其生效,還要保證將其加入到`MockApiTestingModule`中: ```typescript +++ b/first-app/src/app/mock-api/mock-api-testing.module.ts @@ -4,6 +4,7 @@ import {HTTP_INTERCEPTORS, HttpClientModule} from '@angular/common/http'; import {MockApiTestingInterceptor} from '@yunzhi/ng-mock-api/testing'; import {ClazzMockApi} from './clazz.mock.api'; import {TeacherMockApi} from './teacher.mock.api'; +import {StudentMockApi} from './student.mock.api'; @NgModule({ @@ -17,7 +18,8 @@ import {TeacherMockApi} from './teacher.mock.api'; provide: HTTP_INTERCEPTORS, multi: true, useClass: MockApiTestingInterceptor.forRoot([ ClazzMockApi, - TeacherMockApi + TeacherMockApi, + StudentMockApi ]) } ] ``` ## 創建異步驗證器 在`src/app`文件夾中創建一個`YzAsyncValidators`來存放所有的異步驗證器: ```bash panjiedeMacBook-Pro:app panjie$ pwd /Users/panjie/github/mengyunzhi/angular11-guild/first-app/src/app panjiedeMacBook-Pro:app panjie$ ng g class YzAsyncValidators CREATE src/app/yz-async-validators.spec.ts (200 bytes) CREATE src/app/yz-async-validators.ts (35 bytes) ``` 初始化的類如下: ```typescript export class YzAsyncValidators { } ``` 與同步驗證器相同,異步驗證器同樣需要符合某些規范。作為異步驗證器的方法而言,該方法需要返回一個`Observable`,即可被觀察的對象,如果最終該`Observable`發送的為`null`,則表示驗證通過;如果該`Observable`最終發送的為`ValidationErrors`,則說明驗證失敗。 ```typescript import {AbstractControl, ValidationErrors} from '@angular/forms'; import {Observable, of} from 'rxjs'; import {delay} from 'rxjs/operators'; /** * 異步驗證器. */ export class YzAsyncValidators { /** * 驗證方法,學號不存在驗證通過 * @param control FormControl */ static numberNotExist(control: AbstractControl): Observable<ValidationErrors | null> { return of(null) ① .pipe②(delay(1000)③); } } ``` 上述代碼使用了幾個小技巧: - ① `of(null)`方法返回了一個可被訂閱的(可)觀察者`Observable`,該`Observable`發送的數據為`null`。 - ② 在發送數據以前,加入`pipe()`進行處理。 - ③ 處理的方法是延遲1秒鐘再發送數據。 - 所以最終訂閱該`Observable`將在1秒后得到一個值為`null`的數據,這個1秒的延遲模擬了后臺的異步請求。 ## 使用異步驗證器 `FormControl`支持多個驗證器,可以同時使用同步或異步驗證器,同步與異步驗證器的使用方法相同: ```typescript +++ b/first-app/src/app/student/add/add.component.ts @@ -1,6 +1,7 @@ import {Component, OnInit} from '@angular/core'; import {FormControl, FormGroup, Validators} from '@angular/forms'; import {YzValidators} from '../../yz-validators'; +import {YzAsyncValidators} from '../../yz-async-validators'; @Component({ selector: 'app-add', @@ -10,7 +11,7 @@ import {YzValidators} from '../../yz-validators'; export class AddComponent implements OnInit { formGroup = new FormGroup({ name: new FormControl('', Validators.required), - number: new FormControl('', Validators.required), + number: new FormControl('', Validators.required, YzAsyncValidators.numberNotExist??), phone: new FormControl('', YzValidators.phone), email: new FormControl(), clazzId: new FormControl(null, Validators.required) ``` 異步驗證器,放到`FormControl`的第三個參數上??。**如果**只存在異步驗證器而不存在同步驗證器,則可以向第二個參數傳入一個空數組`number: new FormControl('', []??, YzAsyncValidators.numberNotExist)`。 接下來讓我們在異步驗證器上打兩個斷點,來查看異步驗證器的調用時機: ```typescript static numberNotExist(control: AbstractControl): Observable<ValidationErrors | null> { + console.log('異步驗證器被調用'); return of(null) .pipe(delay(1000), tap(data => console.log('驗證器返回數據', data)); } ``` 啟用學生添加組件對應的單元測試: 測試結果一:組件啟動時,學號為空,此時異步驗證器未被調用。得出結論:①異步驗證器在組件啟動時不工作或②異步驗證器在組件值為空時不工作。 測試結果二:輸入學號后,異步驗證器工作,并在1S后接收到返回null時,此時錯誤提示消失,說明學號對應的`FormControl`的`invalid`值為`false`。得出結論:③異步驗證器在組件非空時工作。 ![image-20210413112641398](https://img.kancloud.cn/df/95/df952468070cb04919476a7142c6fd7f_2366x144.png) 測試結果三:填寫學號,然后刪除學號,異步驗證器不工作。得出結論:④異步驗證器在組件值為空時不工作,所以測試結果一中得出的結論①可能是錯誤的。 測試結果四:輸入學號后,異步驗證器開始工作,在尚未返回數據期間,保存按鈕處于可用狀態。得出結論:⑤異步驗證器在未接收到后臺的返回值前,會將表單對應的`FormGroup`的`invalid`值置于`true`。 ![image-20210413112850653](https://img.kancloud.cn/15/f6/15f64b19166b881b60330d84f722ee3c_620x166.png) 測試結果五:快速的輸入學號,比如輸入1234567,異步驗證器將被調用7次,但只會訂閱一個返回結果。得出結論:⑥`FormControl`的值每改變一次,則會調用一次異步驗證器,但如果異步驗證器沒有及時地接收到后臺的結果,則只會獲取最后一次的值。 ![image-20210413113312856](https://img.kancloud.cn/71/61/71617b64c06df2222218d8a83dad6a24_1000x112.png) 上述結論只是我們根據現像的猜想,其實大多數時候猜想如果能解決我們當下遇到的問題,也是完完全全可以的。而如果能在猜想以后再了解到真實的原因,則會對我們知識的提升大有幫助! 真實的原因是這樣: - 當`FormControl`即存在同步驗證器,又存在異步驗證器時。只有當所有的同步驗證器全部通過后,才會調用異步驗證器。所以組件初始化時異步驗證器未調用的真實原因是:存在同步驗證器`Validators.required`,當內容為空時,該驗證器未通過,所以異步驗證器未被調用。 - 在異步驗證器發起請求而未接收到返回值前,`FormControl`的`pending`字段的值將被設置為`true`,接收到返回值后,`FormControl`的`pending`字段的值將被設置為`false`。 - 一個`FormGroup`中的任意`FormControl`的`pending`值為`true`時,該`FormGroup`中的`pengding`為`true`。 - `FormGroup`中的`pending`值為`true`時,其`invalid`值為`false`。 - 異步驗證器在進行驗證時,將忽略歷史上尚未返回的請求值,只使用最近一次請求成功的值。 以上便是發生上述問題的原因。 ## 返回類型為方法 真實的異步驗證器需要進行后臺的請求,所以必然需要`HttpClient`來幫忙發起請求。所以我們在驗證器上需要注入`HttpClient`: ```typescript export class YzAsyncValidators { + + constructor(private httpClient: HttpClient) { + } + ``` 但在`numberNotExist`方法上使用`httpClient`則會碰到一個根本的問題:無法在靜態方法上調用對象中的屬性: ![image-20210413134910123](https://img.kancloud.cn/b0/10/b010155968180b43601992ee062a3ef1_2014x348.png) 若想在異步驗證器上使用注入到對象的`HttpClient`,則需要一些小技巧: ```typescript export class YzAsyncValidators { constructor(private httpClient: HttpClient) { } /** * 驗證方法,學號不存在驗證通過 * @param control FormControl */ ?? numberNotExist(): (control: AbstractControl) => Observable<ValidationErrors | null> ① { return ②(control: AbstractControl): Observable<ValidationErrors | null> => { console.log(this.httpClient); console.log('異步驗證器被調用'); return of(null) .pipe(delay(1000), tap(data => console.log('驗證器返回數據', data))); }; } } ``` - ??該方法不再聲明為`static` - ① 將返回值的類型設置為**方法**。該方法接收一個參數,類型為`AbstractControl`,該方法的返回值類型為:`Observable<ValidationErrors | null>` - ② `return`返回一個**方法**,**該方法**的參數類型與返回值類型與`numberNotExist()`方法聲明的**返回方法**相同。 如果你是第一次將某個方法做為返回值可能會有些不適應。其實返回值類型為方法或是對象或是普通的類型并沒什么不同: ```typescript // 返回值類型為number a(): number { return 1; } // 返回值類型為string a(): string { return '1'; } // 返回值類型為對象 a(): {a: number} { return {a: 123} } // 返回值類型為方法。返回方法的參數為空,返回方法的返回值類型為number a(): () => number { return (): number => { return 3; } } // 返回值類型為方法。返回方法的參數為空,返回方法的返回值類型為string a(): () => string { return (): string => { return '3'; } } // 返回值類型為方法。返回方法的參數有1個,參數類型為number,返回方法的返回值類型為number a(): (a: number) => number { return (a: number): number => { return a + a; } } // 返回值類型為方法。返回方法的參數有1個,參數類型為number,返回方法的返回值類型為string a(): (a: number) => string { return (a: number): string => { return a.toString(); } } ``` ## 單元測試 在`YzAsyncValidators`的構造函數中加入`HttpClient`后,原來的單元測試會一個構造函數異常: ```typescript describe('YzAsyncValidators', () => { it('should create an instance', () => { expect(new YzAsyncValidators()).toBeTruthy(); }); }); ``` 出錯的原因也很簡單:我們剛剛為`YzAsyncValidators`的構造函數指定了一個`HttpClient`參數,但在單元測試時進行實例化時卻沒有傳入這個參數。下面讓我們啟用該單元測試,獲取一個`HttpClient`實例后解決該錯誤: ```typescript +++ b/first-app/src/app/yz-async-validators.spec.ts @@ -1,7 +1,16 @@ import { YzAsyncValidators } from './yz-async-validators'; +import {TestBed} from '@angular/core/testing'; +import {HttpClient, HttpClientModule} from '@angular/common/http'; describe('YzAsyncValidators', () => { - it('should create an instance', () => { - expect(new YzAsyncValidators()).toBeTruthy(); + fit('should create an instance', async?? () => { + // 配置動態測試模塊 + await TestBed.configureTestingModule({ + imports: [HttpClientModule] + }); + // 獲取動態測試模塊中可被注入的HttpClient實例 + const httpClient = TestBed.inject(HttpClient); + + expect(new YzAsyncValidators(httpClient)).toBeTruthy(); }); }); ``` - 在此偷偷的加入了與`await`配合使用的`async`關鍵字 單元測試錯誤消失: ![image-20210413151249679](https://img.kancloud.cn/dc/1b/dc1b644506f50489aa4ad95dc6305051_746x102.png) ## 應用驗證器 調用`YzAsyncValidators`上的`numberNotExist()`則可獲取到一個異步驗證器。去除`numberNotExist()`方法前的`static`關鍵字后,該方法由一個類的靜態方法變更為了一個對象的非靜態方法,所以以下代碼已經不適用了: ```typescript number: new FormControl('', Validators.required, YzAsyncValidators.numberNotExist??), ``` 為此,我們刪除實例化中的相關代碼: ```typescript +++ b/first-app/src/app/student/add/add.component.ts formGroup = new FormGroup({ name: new FormControl('', Validators.required), - number: new FormControl('', Validators.required, YzAsyncValidators.numberNotExist), + number: new FormControl('', Validators.required), phone: new FormControl('', YzValidators.phone), email: new FormControl(), clazzId: new FormControl(null, Validators.required) ``` 當`numberNotExist()`方法變更為對象的方法后,若想調用該方法,則首先需要一個對象,然后將該對象`numberNotExist()`方法值應用到`FormControl`上。為此,我們對組件的代碼進行簡單的改造: ```typescript export class AddComponent implements OnInit { formGroup: FormGroup; constructor() { this.formGroup = new FormGroup({ name: new FormControl('', Validators.required), number: new FormControl('', Validators.required), phone: new FormControl('', YzValidators.phone), email: new FormControl(), clazzId: new FormControl(null, Validators.required) }); } ``` 然后我們嘗試獲取一個`YzAsyncValidators`實例,并將其方法的返回值做為異步驗證器來設置學號對應的`FormControl`: ```typescript +++ b/first-app/src/app/student/add/add.component.ts @@ -1,6 +1,7 @@ import {Component, OnInit} from '@angular/core'; import {FormControl, FormGroup, Validators} from '@angular/forms'; import {YzValidators} from '../../yz-validators'; +import {YzAsyncValidators} from '../../yz-async-validators'; @Component({ selector: 'app-add', @@ -11,9 +12,10 @@ export class AddComponent implements OnInit { formGroup: FormGroup; constructor() { + const yzAsyncValidators = null as unknown as YzAsyncValidators; ?? this.formGroup = new FormGroup({ name: new FormControl('', Validators.required), - number: new FormControl('', Validators.required), + number: new FormControl('', Validators.required, yzAsyncValidators.numberNotExist()①), phone: new FormControl('', YzValidators.phone), email: new FormControl(), clazzId: new FormControl(null, Validators.required) ``` - ① 這是使用的是`yzAsyncValidators.numberNotExist()`而非`yzAsyncValidators.numberNotExist` - 我們在這里使用了`as unknown as YzAsyncValidators`進行類型的強制轉換,這使得原本為`null`的值被做為了`YzAsyncValidators`類型來處理。這明顯是有風險的,在啟用單元測試對組件進行測試時,則會顯露出該風險點:?? ![image-20210413151503548](https://img.kancloud.cn/7e/2a/7e2ad2baa4832015b94c2e938f39f85c_1456x214.png) 上圖報了一個類型錯誤說:你不是說`yzAsyncValidators`的類型是`YzAsyncValidators`嗎?怎么在這個類型上執行①`numberNotExist()`方法時卻不行?所以在開發過程中,除非我們對類型非常的有把握,否則一定**不**要使用`as unknown as Xxxx`來進行強制轉換。 好了,暫時就到這里。下一節中,我們來展示如何在組件中注入`YzAsyncValidators`并完善一下細節。 | 名稱 | 鏈接 | | -------------- | ------------------------------------------------------------ | | 創建異步驗證器 | [https://angular.cn/guide/form-validation#creating-asynchronous-validators](https://angular.cn/guide/form-validation#creating-asynchronous-validators) | | 本節源碼 | [https://github.com/mengyunzhi/angular11-guild/archive/step7.2.3.zip](https://github.com/mengyunzhi/angular11-guild/archive/step7.2.3.zip) |
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看