web-dev-qa-db-fra.com

Angular 6 La vue n'est pas mise à jour après la modification d'une variable dans subscribe

Pourquoi la vue n'est-elle pas mise à jour lorsqu'une variable est modifiée dans un abonnement?

J'ai ce code:

example.component.ts

testVariable: string;

ngOnInit() {
    this.testVariable = 'foo';

    this.someService.someObservable.subscribe(
        () => console.log('success'),
        (error) => console.log('error', error),
        () => {
            this.testVariable += '-bar';

            console.log('completed', this.testVariable);
            // prints: foo-Hello-bar
        }
    );

    this.testVariable += '-Hello';
}

example.component.html

{{testVariable}}

Mais la vue affiche: foo-Hello .

Pourquoi ne pas afficher: foo-Hello-bar ?

Si j'appelle ChangeDetectorRef.detectChanges() dans l'abonnement, la valeur appropriée sera affichée, mais pourquoi dois-je le faire?

Je ne devrais pas appeler cette méthode depuis chaque abonné, ni même du tout (angular devrait le gérer). Y a-t-il un bon moyen?

Ai-je oublié quelque chose dans la mise à jour de Angular/rxjs 5 à 6?

À l'heure actuelle, j'ai Angular version 6.0.2 et rxjs 6.0.0. Le même code fonctionne bien dans Angular 5.2 et dans rxjs 5.5.10 sans qu'il soit nécessaire d'appeler detectChanges.

15
Danny Mencos

Autant que je sache, Angular ne met à jour la vue que si vous modifiez des données dans la "zone angulaire". L'appel asynchrone de votre exemple ne remplit pas ces conditions. Mais si vous le souhaitez, vous pouvez le mettre dans la zone Angular ou bien utiliser rxjs ou extraire une partie du code dans un nouveau composant pour résoudre ce problème. Je vais tout expliquer:

EDIT: Il semble que toutes les solutions ne fonctionnent plus. Pour la plupart des utilisateurs, la première solution "Zone angulaire" fait le travail.

1 Angular Zone

L'utilisation la plus courante de ce service consiste à optimiser les performances lors du démarrage d'un travail consistant en une ou plusieurs tâches asynchrones ne nécessitant pas de mise à jour de l'interface utilisateur ni de gestion des erreurs par Angular. Ces tâches peuvent être lancées via runOutsideAngular et si nécessaire, elles peuvent entrer de nouveau dans la zone Angular via ) . - https://angular.io/api/core/NgZone

La partie clé est la fonction "run". Vous pouvez injecter NgZone et placer votre mise à jour de valeur dans le rappel d'exécution de l'objet NgZone:

constructor(private ngZone: NgZone ) { }
testVariable: string;

ngOnInit() {
   this.testVariable = 'foo';

   this.someService.someObservable.subscribe(
      () => console.log('success'),
      (error) => console.log('error', error),
      () => {
      this.ngZone.run( () => {
         this.testVariable += '-bar';
      });
      }
   );
}

Selon this answer, l'application entière détecte les modifications alors que votre approche ChangeDetectorRef.detectChanges ne détecte que les modifications apportées à votre composant et à ses descendants.

2 RxJS

Une autre solution consisterait à utiliser rxjs pour mettre à jour la vue. Lorsque vous vous abonnez pour la première fois à un ReplaySubject , il vous donnera la dernière valeur. Un BehaviorSubject est fondamentalement le même, mais vous permet de définir une valeur par défaut (est logique dans votre exemple, mais ne constitue pas nécessairement le bon choix à tout moment). Après cette émission initiale, s’agit-il d’un sujet Replay normal:

this.testVariable = 'foo';
testEmitter$ = new BehaviorSubject<string>(this.testVariable);


ngOnInit() {

   this.someService.someObservable.subscribe(
      () => console.log('success'),
      (error) => console.log('error', error),
      () => {
         this.testVariable += '-bar';
         this.testEmitter.next(this.testVariable);
      }
   );
}

A votre avis, vous pouvez vous abonner à Subject en utilisant le tube asynchrone :

{{testEmitter$ | async}}

3 Extrayez le code dans le nouveau composant

Si vous soumettez la chaîne à un autre composant, elle sera également mise à jour. Vous devrez utiliser le sélecteur @Input() dans le nouveau composant.

Donc, le nouveau composant a un code comme celui-ci:

@Input() testVariable = '';

Et la variable testVariable est affectée dans le code HTML comme avant avec des boucles frisées.

Dans la vue HTML parent, vous pouvez ensuite transmettre la variable du parentelement à l'élément enfant:

<app-child [testVariable]="testVariable"></app-child>

De cette façon, vous vous trouvez dans la zone Angular).

4 Préférence personnelle

Ma préférence personnelle est d'utiliser le rxjs ou le composant. Utiliser detectChanges ou NGZone me semble plus sordide.

24
besserwisser