非同步驗證器防抖的補充 以及 記錄動態元件遇到的問題

weiewiyi發表於2022-04-12

補充

這裡對上文的非同步驗證器防抖做一點補充。
https://segmentfault.com/a/11...

vehicleBrandNameNotExist(): AsyncValidatorFn {
    return (control: AbstractControl): Observable<ValidationErrors | null> => {
      if (control.value === '') {
        return of(null);
      }
      return timer(this.debounceTime).pipe(
        // 呼叫服務, 獲取結果
        switchMap(() => this.vehicleBrandService.existByName(control.value)),
        // 對結果進行處理,null表示正確,物件表示錯誤
        map((exists: boolean) => (exists ? {vehicleBrandNameExist: true} : null)),
      )
    };
  }

上文的最後提到,發現這種寫法可以。經過老師的判斷後,發現幾種寫法的關鍵在於一個問題:

每次獲得新值的時候,fromControl是否會取消對上一個資料來源的訂閱?

這種問題從原始碼中找比較困難,於是我搜尋了相關資料:

找到一篇同樣介紹這種方法的文章。

如下圖,同樣用的是time().pipe, 他的解釋說,

當有新的control值時,fromControl會取消對上一個正在等待完成的observable的訂閱,因此,舊的observable不會發出值。

image.png

我畫了一張圖來說明:

看圖就可以瞭解到,fromControl取消了對舊值的訂閱,舊的observable並不會發出值。

image.png


簡化

明白了這個道理之後,防抖就可以簡化成這種寫法:

vehicleTypeNameIsAvailable(VehicleTypeId?: number): AsyncValidatorFn {
    return (control: AbstractControl): Observable<ValidationErrors | null> => {
      if (control.value === '') {
        return of(null);
      }

      return of(null).pipe(
        delay(1000),
        // 呼叫服務, 獲取結果
        switchMap(() => this.vehicleTypeService.nameIsAvailable(control.value, VehicleTypeId)),
        // 對結果進行處理,null表示正確,物件表示錯誤
        map((available: boolean) => (available ? null : {vehicleTypeNameNotAvailable: true})),
      )
    };
  }

直接返回null作為observable, 如果delay(1000),通過了,那就呼叫service服務。

經過測試,執行得很好,符合預期。

後臺遇到的問題

起因是這樣的:
是這樣的,我在user實體新增了Ding欄位,作為推送所有客戶端資訊的釘釘。
image.png


但是在登入的時候發現,只要登入的使用者裡有這個欄位,後臺就報錯
image.png


報錯如下:
image.png

image.png

報了HTTP,和棧溢位等方面的錯誤。看不大懂,於是去谷歌:
image.png

搜了很多,大部分都是說因為新增了 many to One,many to many 等關係, Serializable 序列化或者反序列化導致棧溢位,

建議是:新增@JsonIgnore ,雖然可行,沒有報錯,但是新增之後前臺就接受不到這個欄位了,問題是我需要這個欄位。

後來解決無果,老師看之後,才發現,c層的請求沒有新增@JsonView,這才導致出錯。

新增之後才解決問題。

總結:先檢視哪個請求報的錯,看看相關程式碼有無問題,不行了再谷歌,因為有時候谷歌並不能很好地解決自己的問題。

動態元件遇到的問題

問題

v層是這樣:

<form [formGroup]="formGroup" *ngIf="task">
 <....> // 這裡是 描述<imput>
 <....> // 這裡是 有無截止日期<select>
 <....> // 這裡是  截止日期<input>

  <!--  動態表單項-->
  <div class="ad-banner-example">
    <ng-template appFormItem></ng-template>
  </div>
</form>

按理來是這樣:應該顯示紅框中的內容,即我上述程式碼寫的動態表單
<ng-template appFormItem></ng-template>

image.png


但是結果是這樣:動態表單並沒有顯示出來。

image.png


展示程式碼

這裡展示一下程式碼配置:

c層設定了一個這個屬性,可以往其中新增動態表單項。

@ViewChild(FormItemDirective, {static: true})
appFormItem!: FormItemDirective;

這個是指令定義,用其中的viewContainerRef往裡新增動態表單元件。

@Directive({
  selector: '[appFormItem]'
})
export class FormItemDirective {
  constructor(public viewContainerRef: ViewContainerRef) { }
}

用圖表示是這樣:

image.png


分析問題

我在控制檯中得到了這樣的報錯:

image.png

也就是說,父元件通過 @viewChild 訪問到子元件的屬性 是 undefined!

先來看看什麼是@ViewChild

@ViewChild是一個屬性裝飾器。它提供了一個強大的方式來訪問子元素和屬性。當我們想要訪問父元件中的子元件元素、表單屬性髒檢查、觸發事件,以及父元件中的指令時,@ViewChild是一種非常簡單的訪問方式。@ViewChild是一種非常簡單的訪問子元素的方法。

為什麼會出現這種訪問子元素屬性是undefined的情況呢?我查詢了 @viewChild 的相關資料。

我得到了這麼一個資料:

當父元件開始渲染時,ViewChild裝飾器不可用。它將不會在ngOnInit()生命週期鉤子中可用。它將在ngAfterViewInit生命週期鉤子中可用。

看到這,明白了報錯原因

在ngOnInit的期間我們訪問了 @viewChild ,但是它不可用,所有控制檯報了錯


新的問題

那麼新的問題來了:
即使它在ngOnInit不可用,但是在ngOnInit後,angular 難道不會再渲染一次,使它可用嗎?

我的意思是:當頁面穩定下來後,我們能看到它可用。

這裡又涉及到 @ViewChild 的另一個屬性: static

@ViewChild(FormItemDirective, {static: true})

在我的程式碼中,設定它為static。

static

staic屬性有什麼用? 我找了相關內容

Static stands for whether the ViewChild is “static” content e.g. Always available on the page, no matter page bindings, API calls, ngIfs etc. When set to true, we are telling Angular that the ViewChild will be available at anytime, so simply query for the ChildComponent at the earliest lifecycle hook available and then never query again.

簡單來說就是:

Static 代表 ViewChild 是否是“靜態”內容.

  • 設定為 true 時,我們告訴Angular ViewChild將隨時可用,因此只需在可用的最早生命週期鉤子查詢並載入,然後再也不查詢。
  • 設定為 false,我們是說 ViewChild 將在以後可用,因此我們必須檢查每次執行時的 ViewChild,若不可用,則載入。明顯,這會產生更高的效能負載,因為我們必須始終檢查在元件更改時它是否可用。

來源:
https://tutorialsforangular.c...

暫時不知道這個負載有多大。

會有兩個結果:

  1. 設定為true, 在 ngOnInit 時 ViewChild 可用,但是以後都不再查詢和載入。
  2. 設定為false, 在 ngOnInit 時不可用,在元件變更等訪問操作時,查詢,若不可用,則載入。

理解了含義之後,來找找我自己程式碼的問題。

我發現了問題所在:
image.png

問題原因:在static為true時,使用了*ngIf。

在ngOnInit時,task實體沒有值,所以不會載入下面的子元件。

解決方案:

  • task有值時手動渲染一遍。
  • 把 static 改為 false , 每次用之前查詢一遍,若未載入則載入。但是對效能有一定影響。

經過測試,兩種方法都能很好的生效。我用了第一種。

總結建議

  • 若想在 ngOnIni t中使 @ViewChild 可用 ,並節約效能,或者在頁面上始終可用,並且從不隱藏, static 設為true , 但要注意是否能載入成功。
  • 若想以某種方式動態地呼叫,或者想每次都查詢是否可用, static 設定 false,

相關文章