Angular 2 – Affichage non mis à jour après les modifications du modèle

J’ai un composant simple qui appelle une api REST toutes les quelques secondes et reçoit des données JSON. Je peux voir à partir de mes instructions de journal et du trafic réseau que les données JSON renvoyées changent et que mon modèle est en cours de mise à jour, cependant, la vue ne change pas.

Mon composant ressemble à:

import {Component, OnInit} from 'angular2/core'; import {RecentDetectionService} from '../services/recentdetection.service'; import {RecentDetection} from '../model/recentdetection'; import {Observable} from 'rxjs/Rx'; @Component({ selector: 'recent-detections', templateUrl: '/app/components/recentdetection.template.html', providers: [RecentDetectionService] }) export class RecentDetectionComponent implements OnInit { recentDetections: Array; constructor(private recentDetectionService: RecentDetectionService) { this.recentDetections = new Array(); } getRecentDetections(): void { this.recentDetectionService.getJsonFromApi() .subscribe(recent => { this.recentDetections = recent; console.log(this.recentDetections[0].macAddress) }); } ngOnInit() { this.getRecentDetections(); let timer = Observable.timer(2000, 5000); timer.subscribe(() => this.getRecentDetections()); } } 

Et ma vue ressemble à:

 

Recently detected

Recently detected devices

Id Vendor Time Mac
{{detected.broadcastId}} {{detected.vendor}} {{detected.timeStamp | date:'yyyy-MM-dd HH:mm:ss'}} {{detected.macAddress}}

Je peux voir dans les résultats de console.log(this.recentDetections[0].macAddress) que l’object recentDetections est en cours de mise à jour, mais la table dans la vue ne change jamais à moins que je ne recharge la page.

J’ai du mal à voir ce que je fais mal ici. Quelqu’un peut-il aider?

Il se peut que le code de votre service sorte d’une manière ou d’une autre de la zone d’Angular. Cela rompt la détection des changements. Cela devrait fonctionner:

 import {Component, OnInit, NgZone} from 'angular2/core'; export class RecentDetectionComponent implements OnInit { recentDetections: Array; constructor(private zone:NgZone, // <== added private recentDetectionService: RecentDetectionService) { this.recentDetections = new Array(); } getRecentDetections(): void { this.recentDetectionService.getJsonFromApi() .subscribe(recent => { this.zone.run(() => { // <== added this.recentDetections = recent; console.log(this.recentDetections[0].macAddress) }); }); } ngOnInit() { this.getRecentDetections(); let timer = Observable.timer(2000, 5000); timer.subscribe(() => this.getRecentDetections()); } } 

Pour d’autres moyens d’invoquer la détection des modifications, voir Déclenchement manuel de la détection des modifications dans Angular

Les moyens alternatifs d’invoquer la détection de changement sont

 ChangeDetectorRef.detectChanges() 

exécuter immédiatement la détection des modifications pour le composant actuel et ses enfants

 ChangeDetectorRef.markForCheck() 

inclure le composant actuel lors de la prochaine détection de modifications angulars

 ApplicationRef.tick() 

exécuter la détection de changement pour toute l’application

C’est à l’origine une réponse dans les commentaires de @Mark Rajcok, mais je veux le placer ici comme une solution testée et exploitée en utilisant ChangeDetectorRef , je vois un bon point ici:

Une autre alternative consiste à injecter ChangeDetectorRef et à appeler cdRef.detectChanges() au lieu de zone.run() . Cela pourrait être plus efficace, car il ne lancera pas de détection de modifications sur toute l’arborescence des composants, comme le fait zone.run() . – Mark Rajcok

Donc le code doit être comme:

 import {Component, OnInit, ChangeDetectorRef} from 'angular2/core'; export class RecentDetectionComponent implements OnInit { recentDetections: Array; constructor(private cdRef: ChangeDetectorRef, // <== added private recentDetectionService: RecentDetectionService) { this.recentDetections = new Array(); } getRecentDetections(): void { this.recentDetectionService.getJsonFromApi() .subscribe(recent => { this.recentDetections = recent; console.log(this.recentDetections[0].macAddress); this.cdRef.detectChanges(); // <== added }); } ngOnInit() { this.getRecentDetections(); let timer = Observable.timer(2000, 5000); timer.subscribe(() => this.getRecentDetections()); } } 

Edit : L’utilisation de .detectChanges() intérieur de subscibe peut entraîner un problème de création Tenter d’utiliser une vue détruite: detectChanges

Pour le résoudre, vous devez vous unsubscribe avant de détruire le composant, le code complet sera donc le suivant:

 import {Component, OnInit, ChangeDetectorRef, OnDestroy} from 'angular2/core'; export class RecentDetectionComponent implements OnInit, OnDestroy { recentDetections: Array; private timerObserver: Subscription; constructor(private cdRef: ChangeDetectorRef, // <== added private recentDetectionService: RecentDetectionService) { this.recentDetections = new Array(); } getRecentDetections(): void { this.recentDetectionService.getJsonFromApi() .subscribe(recent => { this.recentDetections = recent; console.log(this.recentDetections[0].macAddress); this.cdRef.detectChanges(); // <== added }); } ngOnInit() { this.getRecentDetections(); let timer = Observable.timer(2000, 5000); this.timerObserver = timer.subscribe(() => this.getRecentDetections()); } ngOnDestroy() { this.timerObserver.unsubscribe(); } } 

Essayez d’utiliser @Input() recentDetections: Array;

EDIT: La raison pour laquelle @Input() est important est que vous souhaitez lier la valeur du fichier typescript / javascript à la vue (html). La vue se mettra à jour si une valeur déclarée avec le décorateur @Input() est modifiée. Si un @Input() ou @Output() est modifié, un ngOnChanges ngOnChanges se déclenchera et la vue se mettra à jour avec la nouvelle valeur. Vous pouvez dire que le @Input() liera la valeur dans les deux sens.

recherche de Input sur ce lien par angular: glossaire pour plus d’informations

EDIT: après en avoir appris plus sur le développement de Angular 2, je me suis dit que faire un @Input() vraiment pas la solution, et comme mentionné dans les commentaires,

@Input() s’applique uniquement lorsque les données sont modifiées par une liaison de données en dehors du composant (les données liées dans le parent sont modifiées) et non lorsque les données sont modifiées à partir du code dans le composant.

Si vous regardez @ la réponse de Günter, c’est une solution plus précise et correcte au problème. Je vais toujours garder cette réponse ici, mais veuillez suivre la réponse de Günter comme étant la bonne.

Dans mon cas, j’ai eu un problème très similaire. Je mettais à jour ma vue dans une fonction appelée par un composant parent et dans mon composant parent, j’ai oublié d’utiliser @ViewChild (NameOfMyChieldComponent). J’ai perdu au moins 3 heures juste pour cette erreur stupide. ie: je n’ai pas eu besoin d’utiliser l’ une de ces méthodes:

  • ChangeDetectorRef.detectChanges ()
  • ChangeDetectorRef.markForCheck ()
  • ApplicationRef.tick ()

Au lieu de gérer les zones et la détection des modifications, laissez AsyncPipe gérer la complexité. Cela mettra un abonnement observable, une désinscription (pour éviter les memory leaks) et modifiera la détection sur les épaules angulars.

Changez votre classe pour en faire une observable, qui émettra des résultats de nouvelles requêtes:

 export class RecentDetectionComponent implements OnInit { recentDetections$: Observable>; constructor(private recentDetectionService: RecentDetectionService) { } ngOnInit() { this.recentDetections$ = Observable.interval(5000) .exhaustMap(() => this.recentDetectionService.getJsonFromApi()) .do(recent => console.log(recent[0].macAddress)); } } 

Et mettez à jour votre vue pour utiliser AsyncPipe:

  ...  

Vous voulez append qu’il est préférable de faire un service avec une méthode qui prendra des arguments d’ interval et:

  • créer de nouvelles requêtes (en utilisant exhaustMap comme dans le code ci-dessus);
  • gérer les demandes d’erreurs;
  • arrêtez le navigateur de faire de nouvelles requêtes hors ligne.