Angular - 解決ViewChild undefined 問題

當我們在Angular中想要操作Template的DOM時,我們通常會在該DOM上加入範本參考變數(Template reference variables)

再經由ViewChild去取得,但並不是所有情況都可以取得到畫面上的DOM,因此本文主要介紹為什麼會遇到undefined問題

以及如何解決。

前陣子剛好接觸一個Angular專案,該專案需要在彈跳視窗中能在input輸入框中輸入內容,並根據關鍵字進行篩選。

這功能其實非常常見,筆者自己慣用的方法主要是透過Observable來訂閱該輸入框,並能即時取得使用者所輸入的內容。

所以一般程式碼大概會長得像這個樣子:

Template:

    <input type="text" [(ngModel)]="inputSearch" #inputKey="ngModel">

Component:

export class InputSearchComponent implements  AfterViewInit {

  @ViewChild('inputKey') key: NgModel;
  constructor() { }
  
  inputSearch;

  ngAfterViewInit(): void {
   
    this.key.valueChanges.pipe(debounceTime(1000)).subscribe(x => console.log(x));
  }

但正當我在該專案寫上這樣的Code時,卻回報該錯誤訊息:

看到錯誤訊息的當下還一度以為自己是不是哪裡打錯了,以往都不會發生的錯誤竟然在這裡發生。

後來進一步追查程式碼後,發現其實是因為 ngIf在搞鬼。

因為我的專案需求是當彈跳視窗出現時,使用者可以根據輸入框填寫關鍵字,進而查詢所需要的資訊。

在這裡我使用了ngIf,當按鈕按下後,該彈跳視窗就可以顯示在畫面上,相反的如果條件不成立,則不顯示。

而其中ViewChild在Component當中,只會執行一次,也就是當畫面載入完成時,會透過範本參考變數來抓取我們的DOM。

問題就出在這個地方,因為ngIf並不是直接將該區塊隱藏起來(display:none),而是直接將該區塊加入/銷毀。

因此在一開始ngIf條件為false時,該區塊並不會顯示在整個DOM樹上。

所以ViewChild取不到物件,自然就會回傳undefined。而當undefined後,又去呼叫valueChanges當然就沒有這個方法啦!,所以才會回報錯誤訊息。

那,現在我們知道問題發生的點了,我們可以怎麼做?

筆者這裡提供兩個解法供讀者們使用

1. 改用hidden

在Angular中,有提供屬性[hidden]在這邊我們一樣可以透過條件式決定是否顯示,此外由於是隱藏(display:none)的關係,因此在DOM上還是存在的,

所以ViewChild就可以透過範本參考變數取得我們所需的DOM元件。

2.透過ChangeDetectorRef 再次偵測畫面上的DOM樹

由於剛剛我們有提到ViewChild只會在第一次的時候抓取DOM樹,因此即使後面ngIf條件為True可以顯示內容時,仍然不會去抓取,因此我們可以透過ChangeDetectorRef類別

當我們注入ChangeDetectorRef類別後,我們可以透過 detectChanges() 方法,偵測DOM樹有無變更,若變更的話,ViewChild就會在進行一次抓取

透過這樣的方式我們就可以取得畫面上的元件了,無論該DOM是否有在畫面上。

 

以上文章敘述如有錯誤及觀念不正確,請不吝嗇指教:)

有任何家教、案子 或技術相關問題 請都歡迎聯繫我

http://www.zhenghui.idv.tw/