iT邦幫忙

2024 iThome 鐵人賽

DAY 26
0
JavaScript

Signal API in Angular系列 第 26

Day 26 - 將 Decorators遷移到 input、queries 和 output 函數

  • 分享至 

  • xImage
  •  

input()output()viewChild()viewChildren()contentChild()contentChildren()outputFromObservable()outputToObservable() 處於開發者預覽狀態 (developer preview)。因此,專家不建議在 production application 中使用它們。 Production application 應繼續使用裝飾器 (decorators),直到上述功能進入穩定狀態。

出現一個問題,開發團隊將負責遷移這些裝飾器和函數。 這些工作需要對任何規模的 Angular 專案進行規劃 (planning)、實施 (implementation) 和測試 (testing) 。

幸運的是,ngxtension 函式庫提供了幾個可以在專案中執行自動遷移的 schematics。Schematics 包括訊signal inputs、new ouputs 和 queries migrations。

讓我們在這篇文章中逐步進行遷移。

您可以在此 feature branch 中找到裝飾器 (decorator) 版本,https://github.com/railsstudent/ng-signal-migration-demo/tree/feat/decorators。

例子 1:遷移到訊號輸入和新輸出

@Component({
 selector: 'app-some',
 standalone: true,
 imports: [FormsModule],
 template: `
   <div>
     <p>bgColor: {{ bgColor }}</p>
     <p>name: {{ name }}</p>
   </div>
 `,
})
export class SomeComponent {
 @Input({ required: true, alias: 'backgroundColor'}) bgColor!: string;
 @Input({ transform: (x: string) => x.toLocaleUpperCase() }) name: string = 'input decorator';
}

SomeComponent 組件使用 Input 裝飾器接收來自父組件的輸入 (input)。 bgColor 是必要的輸入 (input),具有別名 backgroundColorname 是將傳入值轉換為大寫的輸入 (input)。

export class SomeComponent {
 @Output() triple = new EventEmitter<number>();
 @Output('cube') powerXBy3 = new EventEmitter<number>();

 numSub = new BehaviorSubject<number>(2);
 @Output() double = this.numSub.pipe(map((n) => n * 2));
}

同一組件還具有三個 output 裝飾器。 triple 是一個發出數字的 event emitter。 powerXBy3 也是一個帶有別名 cube 的數字 event emitter。 double消耗numSub` behaviorSubject 的值,乘以 2,並將結果傳送給父組件。

現在,在您選擇的任何終端機中執行 ngxtension schematics 來執行遷移。

將 Input 裝飾器移轉到 signal inputs 函數

ng g ngxtension:convert-signal-inputs

遷移後,

bgColor = input.required<string>({ alias: 'backgroundColor' });
name = input<any, string | string>('input decorator', { transform: (x: string) => x.toLocaleUpperCase() });  

對於 bgColor,input.required 函數使用 alias 選項。對於 name signal input,使用初始值和轉換選項來將輸入文字大寫。 對父組件沒有影響。

@Component({
 selector: 'app-some',
 standalone: true,
 imports: [FormsModule],
 template: `
   <div>
     <p>bgColor: {{ bgColor() }}</p>
     <p>name: {{ name() }}</p>
 `,
})
export class SomeComponent {}

bgColor name 是 signal inputs; HTML 範本呼叫 signal 函數來顯示它們的值。

將 Output 裝飾器移轉到 output 函數

ng g ngxtension:convert-outputs

遷移後,

triple = output<number>();
powerX3 = output<number>({ alias: 'cube' });

numSub = new BehaviorSubject<number>(2);
double = outputFromObservable(this.numSub.pipe(map((n) => n * 2)));

triple output 函數輸出數字。 powerX3 是 output 函數並將別名選項傳遞為第一個參數。別名的值為cube,與之前相同。 double 使用 outputFromObservable 函數將 Observable 轉換為 output 。對父組件沒有影響。

例子 2:遷移查詢裝飾器 (queries decorators)

export class QueriesComponent implements AfterContentInit {
 @ContentChild('header') header!: ElementRef<HTMLDivElement>;
 @ContentChildren('p') body!: QueryList<ElementRef<HTMLParagraphElement>>;
 
 appendHeader = '';
 list = '';

 ngAfterContentInit(): void {
      this.appendHeader = `${this.header.nativeElement.textContent} Appended`;
      this.list = this.body.map((p) => p.nativeElement.textContent).join('---');
 }
}

QueriesComponent 組件會套用 ContentChildContentChildren 裝飾器 (decoratos) 來查詢投影到 <ng-content> 元素的 HTML 元素。

export class AppComponent implements AfterViewInit {
 @ViewChild(QueriesComponent) queries!: QueriesComponent;
 @ViewChildren('a') aComponents!: QueriesComponent[];

 viewChildName = '';
 numAComponents = 0;

 ngAfterViewInit(): void {
    this.viewChildName = this.queries.name; 
    this.numAComponents = this.aComponents.length;
 }
}

AppComponent 組件使用 ViewChild 裝飾器來查詢第一個 QueriesComponent。 它使用 ViewChildren 裝飾器來查詢與範本變數 (template variable) a 相符的所有 QueriesComponent 組件。

將 Queries 裝飾器移轉到 Queries Signals

ng g ngxtension:convert-queries

遷移後,

export class QueriesComponent implements AfterContentInit {
 header = contentChild<ElementRef<HTMLDivElement>>('header');
 body = contentChildren<ElementRef<HTMLParagraphElement>>('p');

 appendHeader = '';
 list = '';

 ngAfterContentInit(): void {
     this.appendHeader = `${this.header()?.nativeElement.textContent} Appended`;
     this.list = this.body().map((p) => p.nativeElement.textContent).join('---');
  }
}

此 schematic 將 ContentChild 裝飾器遷移到具有正確類型的 contentChild 函數。同樣,schematic 將 ContentChildren 裝飾器遷移到 contentChildren 函數。 contentChildcontentChildren 函數傳回一個 signal;因此,ngAfterContentInit 生命週期方法中的程式碼也被修改。

export class AppComponent implements AfterViewInit {
 queries = viewChild(QueriesComponent);
 aComponents = viewChildren<QueriesComponent[]>('a');

 ngAfterViewInit(): void {
    this.viewChildName = this.queries()?.name || ''; 
    this.numAComponents = this.aComponents().length;
 }
}

此 schematic 將 ViewChild 裝飾器遷移到具有正確類型的 viewChild 函數。同樣,schematic 將 ViewChildren 裝飾器遷移到 viewChildren 函數。 viewChildviewChildren 函數傳回一個 signal;因此,ngAfterViewInit 生命週期方法中的程式碼也被修改。

結論:

  • ngxtension:convert-signal-inputs schematic 將 Input 裝飾器移轉到 signal input。
  • ngextension:convert-outputs schematic 將 Output 裝飾器遷移到 outputoutputFromObservable 函數。
  • ngextension:convert-queries schematic 將 ViewChildViewChildrenContentChildContentChildren 裝飾器遷移到 viewChildviewChildrencontentChildcontentChildren 函數。

鐵人賽的第 26 天到此結束。

參考:


上一篇
Day 25 - 使用outputFromObservable函數將Observable轉換為OutputRef
下一篇
Day 27 - Signal for state management
系列文
Signal API in Angular36
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言