Required signal inputs 不能在 constructor 或 field initializers 使用,因為當時該值不可用。為了存取該值,我的解決方案是在 effect 中觀察 signal input 的實際變化,向伺服器發出 HTTP 請求並設定 signal 的值。關於不使用 effect 的討論有很多,我必須找到其他解決方案來刪除它。
Required signal inputs 可在 ngOnInit和ngOnChanges 生命週期方法中存取。但是,toSignal會在其中引發錯誤,因為它們位於context injection` 之外。它可以透過兩種方式修復:
manual injector 傳遞給 toSignal 函數runInInjectionContext 的回呼函數中 (callback function) 執行 toSignal 函數。import { Component, effect, inject, Injector, input, signal } from '@angular/core';
import { getPerson, Person } from './star-war.api';
import { StarWarPersonComponent } from './star-war-person.component';
@Component({
 selector: 'app-star-war',
 standalone: true,
 imports: [StarWarPersonComponent],
 template: `
     <p>Jedi Id: {{ jedi() }}</p> 
     <app-star-war-person [person]="fighter()" kind="Jedi Fighter" />`,
})
export class StarWarComponent {
 // required signal input
 jedi = input.required<number>();
 injector = inject(Injector);
 fighter = signal<Person | undefined>(undefined);
 
 constructor() {
  effect((OnCleanup) => {
     const sub = getPerson(this.jedi(), this.injector)
       .subscribe((result) => this.fighter.set(result));
     OnCleanup(() => sub.unsubscribe());
   });
 }
}
程式碼更改如下:
StarWarService 來呼叫 API 並回傳 Observable。StarWarComponent 實作 OnInit 介面。inject 函數注入元件的 Injector。ngOnInit 中,透過 required signal input 呼叫 StarWar API 並從 Observable 建立signal。將 manual injector 傳遞給 toSignal 函數以避免錯誤。ngOnInit 中,runInInjectionContext 函數呼叫 toSignal 函數。export type Person = {
 name: string;
 height: string;
 mass: string;
 hair_color: string;
 skin_color: string;
 eye_color: string;
 gender: string;
 films: string[];
}
import { HttpClient } from "@angular/common/http";
import { inject, Injectable } from "@angular/core";
import { catchError, Observable, of, tap } from "rxjs";
import { Person } from "./person.type";
const URL = 'https://swapi.dev/api/people';
@Injectable({
 providedIn: 'root'
})
export class StarWarService {
 private readonly http = inject(HttpClient);
 getData(id: number): Observable<Person | undefined> {
   return this.http.get<Person>(`${URL}/${id}`).pipe(
     tap((data) => console.log('data', data)),
     catchError((err) => {
       console.error(err);
       return of(undefined);
     }));
 }
}
建立一個帶有 getData 方法的 StarWarService 來呼叫 StarWar API 來檢索人員。結果是 Observable 或 undefined。
import { Component, input } from '@angular/core';
@Component({
 selector: 'app-star-war',
 standalone: true,
 template: `
  <p>Jedi Id: {{ jedi() }}</p>
  <p>Sith Id: {{ sith() }}</p>
 `,
})
export class StarWarComponent implements OnInit {
 // required signal input
 jedi = input.required<number>();
 // required signal input
 sith = input.required<number>();
 ngOnInit(): void {}
}
jedi 和 sith 都是 required signal inputs;因此,我無法在 constructor 中使用它們或使用服務 (service) 呼叫 toSignal 來初始化欄位 (field initializers)。
我實作了 OnInit 介面並在 ngOnInit 方法中存取兩個 required signal inputs。
import { Component, VERSION } from '@angular/core';
import { StarWarComponent } from './star-war.component';
@Component({
 selector: 'app-root',
 standalone: true,
 imports: [StarWarComponent],
 template: `
   <app-star-war [jedi]="1" [sith]="4" />
   <app-star-war [jedi]="10" [sith]="44" />`,
})
export class App {}
App 元件有兩個 StarWarComponent 實例。 第一個實例的 jedi id 為 1,第二個實例的 jedi id 為 10。 實例的 sith id 分別為 4 和 44。
export class StarWarComponent implements OnInit {
 // required signal input
 jedi = input.required<number>();
 starWarService = inject(StarWarService);
 injector = inject(Injector);
 light!: Signal<Person | undefined>;
}
在 StarWarComponent 元件中,我注入 StarWarService 和元件的 Injector。此外,我聲明了一個 light Signal 來儲存 toSignal 函數傳回的結果。
interface ToSignalOptions<T> {
 initialValue?: unknown;
 requireSync?: boolean;
 injector?: Injector;
 manualCleanup?: boolean;
 rejectErrors?: boolean;
 equal?: ValueEqualityFn<T>;
}
ToSignalOptions 選項有一個 injector,當在 context injection 之外使用 toSignal 函數時,我可以將元件的 Injector 傳遞給該選項。
export class StarWarComponent implements OnInit {
 // required signal input
 jedi = input.required<number>();
 starWarService = inject(StarWarService);
 injector = inject(Injector);
 light!: Signal<Person | undefined>;
 ngOnInit(): void {
   this.light = toSignal(this.starWarService.getData(this.jedi()), { injector: this.injector });
  }
}
在 ngOnInit 方法中,我呼叫服務 (service) 來取得 Observable,並使用 toSignal 函數建立 signal。第二個參數是元件的 Injector 的選項。
<app-star-war-person [person]="light()" kind="Jedi Fighter" />
接下來,我將 light signal 傳遞給 StarWarPersonComponent 元件以顯示絕地戰士的詳細資訊。
export class StarWarComponent implements OnInit {
 // required signal input
 sith = input.required<number>();
 starWarService = inject(StarWarService);
 injector = inject(Injector);
 evil!: Signal<Person | undefined>;
 ngOnInit(): void {
   // this also works
   runInInjectionContext(this.injector, () => {
     this.evil = toSignal(this.starWarService.getData(this.sith()));
   })
 }
}
我聲明了一個 evil 的 Signal 來儲存 toSignal 函數傳回的結果。 runInInjectionContext 的第一個參數是元件的 Injector。第二個參數是一個回呼函數 (callback function),它執行 toSignal 函數並將 person 指派給 evil 變數。
<app-star-war-person [person]="evil()" kind="Sith Lord" />
接下來,我將 evil signal 傳遞給 StarWarPersonComponent 元件以顯示西斯尊主的詳細資料。
如果元件有 required signal inputs,我可以存取 ngOnInit 或 ngOnChanges 中的值來發出 HTTP 請求或其他操作。 然後,我不需要創建 effect 來觀看所需的 required signal inputs。
結論:
ngOnInit 或 ngOnChanges 方法中使用。toSignal 在 ngOnInit 和 ngOnChanges 方法中拋出錯誤,因為它在 injection context 之外運行。ToSignalOptions 的 injector 選項。runInInjectionContext 函數的 callback 中呼叫 toSignal 函數。鐵人賽的第 33 天到此結束。
參考: