首頁  >  文章  >  web前端  >  將手動注入器傳遞給 toSignal 函數以避免外部上下文注入錯誤

將手動注入器傳遞給 toSignal 函數以避免外部上下文注入錯誤

Susan Sarandon
Susan Sarandon原創
2024-09-24 10:30:17198瀏覽

Pass manual injector to the toSignal function to avoid outside Context Injection error

必需的訊號輸入不能在建構函式或欄位初始值設定項中使用,因為該值不可用。要存取該值,我的解決方案是觀察訊號的實際變化,向伺服器發出 HTTP 請求,然後設定訊號的值。關於不使用該效果的討論有很多,我必須找到其他解決方案來刪除它。

所需的訊號輸入可以在 ngOnInit 和 ngOnChanges 生命週期方法中存取。但是,toSignal 會在其中引發錯誤,因為它們位於注入上下文之外。可以用兩種方式修復:

  • 將手動注入器傳遞給 toSignal 函數
  • 在runInInjectionContext的回呼函數中執行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 中,使用所需的訊號輸入呼叫 StarWar API 並從 Observable 建立訊號。為了避免錯誤,請將手動注入器傳遞給 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 或未定義的。

所需訊號輸入

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 {}
}

絕地武士和西斯都需要訊號輸入;因此,我無法在建構函式中使用它們或透過服務呼叫 toSignal 來初始化欄位。

我實作了 OnInit 介面並存取 ngOnInit 方法中的兩個訊號輸入。

準備應用程式元件

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,第二個實例的 id 為 10。實例的 sith id 分別為 4 和 44。

將手動注入器傳遞給 toSignal 來查詢絕地武士

export class StarWarComponent implements OnInit {
 // required signal input
 jedi = input.required<number>();

 starWarService = inject(StarWarService);
 injector = inject(Injector);
 light!: Signal<Person | undefined>;
}

在 StarWarComponent 元件中,我注入了 StarWarService 和元件的注入器。此外,我聲明了一個 light Signal 來儲存 toSignal 函數傳回的結果。

interface ToSignalOptions<T> {
 initialValue?: unknown;
 requireSync?: boolean;
 injector?: Injector;
 manualCleanup?: boolean;
 rejectErrors?: boolean;
 equal?: ValueEqualityFn<T>;
}

ToSignalOptions 選項有一個注入器屬性。當在註入上下文之外使用 toSignal 函數時,我可以將元件的注入器傳遞給選項。

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 方法中,我呼叫服務來取得 Observable,並使用 toSignal 函數建立訊號。第二個參數是組件注入器的選項。

<app-star-war-person [person]="light()" kind="Jedi Fighter" />

接下來,我將光訊號傳遞給 StarWarPersonComponent 組件以顯示絕地武士的詳細資訊。

runInInjectionContext 在元件的注入器中執行 toSignal

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()));
   })
 }
}

我宣告了一個邪惡的 Signal 來儲存 toSignal 函數傳回的結果。 runInInjectionContext 的第一個參數是元件的注入器。第二個參數是一個回呼函數,它執行 toSignal 函數並將 person 指派給邪惡變數。

<app-star-war-person [person]="evil()" kind="Sith Lord" />

接下來,我將邪惡訊號傳遞給 StarWarPersonComponent 元件以顯示西斯尊主的詳細資料。

如果元件需要訊號輸入,我可以存取 ngOnInit 或 ngOnChanges 中的值來發出 HTTP 請求或其他操作。 然後,我不需要創建效果來觀看所需的信號並調用後端。

結論:

  • 所需的訊號輸入無法在建構函式中調用,因為此時該值不可用。
  • 所需的訊號輸入可以在 ngOnInit 或 ngOnChanges 方法中使用。
  • toSignal 在 ngOnInit 和 ngOnChanges 方法中拋出錯誤,因為它在註入上下文之外運行
  • 將手動注入器傳遞給ToSignalOptions的注入器選項
  • 在runInInjectionContext函數的回呼函數中呼叫toSignal函數。

鐵人挑戰賽第 33 天到此結束。

參考資料:

  • toSignal官方文件:https://angular.dev/guide/signals/rxjs-interop#injection-context
  • ToSignalOptions:https://angular.dev/api/core/rxjs-interop/ToSignalOptions#
  • RunInInjectionContext:https://angular.dev/api/core/rxjs-interop/ToSignalOptions#
  • GitHub 問題:https://github.com/angular/angular/issues/50947
  • Stackblitz 示範:https://stackblitz.com/edit/stackblitz-starters-xsitft?file=src%2Fstar-war.component.ts

以上是將手動注入器傳遞給 toSignal 函數以避免外部上下文注入錯誤的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn