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 天到此結束。
參考: