Maison  >  Article  >  interface Web  >  Passez l'injecteur manuel à la fonction toSignal pour éviter une erreur d'injection de contexte extérieur

Passez l'injecteur manuel à la fonction toSignal pour éviter une erreur d'injection de contexte extérieur

Susan Sarandon
Susan Sarandonoriginal
2024-09-24 10:30:17195parcourir

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

L'entrée de signal requise ne peut pas être utilisée dans le constructeur ou l'initialiseur de champ car la valeur n'est pas disponible. Pour accéder à la valeur, ma solution consiste à observer le changement en vigueur du signal, à faire une requête HTTP au serveur et à définir la valeur du signal. Il y a de nombreuses discussions sur la non-utilisation de l'effet, et je dois trouver d'autres solutions pour le supprimer.

Les entrées de signal requises sont accessibles dans les méthodes de cycle de vie ngOnInit et ngOnChanges. Cependant, toSignal génère des erreurs car elles sont en dehors du contexte d'injection. Il peut être corrigé de deux manières :

  • Passer l'injecteur manuel à la fonction toSignal
  • Exécutez la fonction toSignal dans la fonction de rappel de runInInjectionContext.

Utiliser l'entrée de signal en vigueur (à modifier ultérieurement)

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

Les changements de code sont les suivants :

  • Créez un StarWarService pour appeler l'API et renvoyer l'Observable
  • Le StarWarComponent implémente l'interface OnInit.
  • Utilisez la fonction inject pour injecter l'injecteur du composant
  • Dans ngOnInit, appelez l'API StarWar en utilisant l'entrée de signal requise et créez un signal à partir de l'Observable. Pour éviter l'erreur, passez l'injecteur manuel à la fonction toSignal.
  • Dans ngOnInit, la fonction runInInjectionContext appelle la fonction toSignal dans le contexte de l'injecteur.

Créer StarWarService

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

Créez un StarWarService avec une méthode getData pour appeler l'API StarWar afin de récupérer une personne. Le résultat est un Observable d'une personne ou indéfini.

Entrée de signal requise

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

Les Jedi et les Sith sont des entrées de signal requises ; par conséquent, je ne peux pas les utiliser dans le constructeur ni appeler toSignal avec le service pour initialiser les champs.

J'implémente l'interface OnInit et accède aux deux entrées de signal dans la méthode ngOnInit.

Préparer le composant d'application

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

Le composant App a deux instances de StarWarComponent. L'identifiant Jedi de la première instance est 1 et l'identifiant de la deuxième instance est 10. Les identifiants Sith des instances sont respectivement 4 et 44.

Passez l'injecteur manuel à toSignal pour interroger un combattant Jedi.

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

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

Dans le composant StarWarComponent, j'injecte le StarWarService et l'injecteur du composant. De plus, je déclare un signal lumineux pour stocker le résultat renvoyé par la fonction toSignal.

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

L'option ToSignalOptions a une propriété d'injecteur. Lors de l'utilisation de la fonction toSignal en dehors du contexte d'injection, je peux passer l'injecteur du composant à l'option.

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

Dans la méthode ngOnInit, j'appelle le service pour obtenir un Observable, et j'utilise la fonction toSignal pour créer un signal. Le deuxième argument est une option avec l'injecteur du composant.

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

Ensuite, je transmets le signal lumineux au composant StarWarPersonComponent pour afficher les détails d'un combattant Jedi.

runInInjectionContext exécute toSignal dans l'injecteur du composant

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

Je déclare un signal maléfique pour stocker le résultat renvoyé par la fonction toSignal. Le premier argument de runInInjectionContext est l’injecteur du composant. Le deuxième argument est une fonction de rappel qui exécute la fonction toSignal et attribue la personne à la variable maléfique.

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

Ensuite, je transmets le signal maléfique au composant StarWarPersonComponent pour afficher les détails du Seigneur Sith.

Si un composant nécessite des entrées de signal, je peux accéder aux valeurs de ngOnInit ou ngOnChanges pour effectuer des requêtes HTTP ou d'autres opérations. Ensuite, je n'ai pas besoin de créer un effet pour regarder les signaux requis et appeler le backend.

Conclusions :

  • L'entrée de signal requise ne peut pas être appelée dans le constructeur car la valeur n'est pas disponible à ce moment-là.
  • Les entrées de signal requises peuvent être utilisées dans les méthodes ngOnInit ou ngOnChanges.
  • toSignal génère des erreurs dans les méthodes ngOnInit et ngOnChanges car il s'exécute en dehors du contexte d'injection
  • Passez l'injecteur manuel à l'option injecteur de ToSignalOptions
  • Appelez la fonction toSignal dans la fonction de rappel de la fonction runInInjectionContext.

Cela conclut la 33e journée du défi Ironman.

Références :

  • Documentation officielle de 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#
  • Problème GitHub : https://github.com/angular/angular/issues/50947
  • Démo Stackblitz : https://stackblitz.com/edit/stackblitz-starters-xsitft?file=src%2Fstar-war.component.ts

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn