因職位轉換為全端,所以重新學習新的前端框架 angular。公司前端架構目前是 angular js 和 angular 混雜使用,雖然短期內可以順利運作,但長期還是得全部轉換至 angular 才好維護。剛好轉換的工作就讓我這 angular 菜鳥來練練手啦!
這次遇到的 Bug 如下,這是 code base 中已經寫好的 angular directive
@Directive({
selector: '[datePicker]',
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: DatePickerDirective,
multi: true,
},
],
})
export class DatePickerDirective implements OnInit, ControlValueAccessor, OnChanges {
@Input() .....
簡單來說他把 jQuery 的 date picker 套件包成 directive,並宣告成 NG_VALUE_ACCESSOR,這樣就能使用 ngModel 來做綁定
<input type="text"
datePicker
[(ngModel)]="asOfDate"
class="form-control form-date">
但實際測試,發現點擊 input 時 date picker 根本不會出現,為了確定 directive 有效,我在 ngOnInit 的部分加了 log
...
ngOnInit(): void {
console.log("date-picker work!");
...
}
發現這一行根本不會跑!非常的奇怪,由於同一份檔案還有其他類似包裝 jQuery 的 directive,觀察了一下,另一個用過且確定可以動的 directive 並沒有把他宣告成 NG_VALUE_ACCESSOR,看來這個可能是鬼,於是把他註解掉
@Directive({
selector: '[datePicker]',
//providers: [
// {
// provide: NG_VALUE_ACCESSOR,
// useExisting: DatePickerDirective,
// multi: true,
// },
],
})
export class DatePickerDirective implements OnInit, ControlValueAccessor, OnChanges {
@Input() .....
再試一次,date picker 可以正常出現!但日期卻無法綁定,這合理,因為我們拔掉了 NG_VALUE_ACCESSOR。
解法是,在 useExisting 參數使用 fowardRef 來傳遞 class 實體,而不直接傳 class 進去,如下
@Directive({
selector: '[datePicker]',
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => DatePickerDirective),
multi: true,
},
],
})
export class DatePickerDirective implements OnInit, ControlValueAccessor, OnChanges {
@Input() .....
主因是我們在 decorator 裡面引用 DatePickerDirective 時,他順序還在後面沒有宣告到,可能因此導致 angular 出錯,轉變成我們觀察到的現象: directive 根本沒有執行。
但 angular 框架規定要把 NG_VALUE_ACCESSOR 相關的定義放在 decorator 裡面的 providers,此時只好使用 forwardRef 函數作為中介,讓他先取到 forwardRef 函數實體,之後 angular 內部要使用時就可以透過它回傳已經走過宣告的 DatePickerDirective class 實體,如此即能解決宣告順序的問題。
參考資料
[Angular 大師之路] Day 08 – 自訂表單控制項
Forward references in Angular