J'ai un composant simple qui appelle une REST api toutes les quelques secondes et qui reçoit des données JSON. D'après mes instructions de journal et le trafic réseau, les données JSON renvoyées changent et mon modèle est en cours de mise à jour. Toutefois, 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<RecentDetection>;
constructor(private recentDetectionService: RecentDetectionService) {
this.recentDetections = new Array<RecentDetection>();
}
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 à:
<div class="panel panel-default">
<!-- Default panel contents -->
<div class="panel-heading"><h3>Recently detected</h3></div>
<div class="panel-body">
<p>Recently detected devices</p>
</div>
<!-- Table -->
<table class="table" style="table-layout: fixed; Word-wrap: break-Word;">
<thead>
<tr>
<th>Id</th>
<th>Vendor</th>
<th>Time</th>
<th>Mac</th>
</tr>
</thead>
<tbody >
<tr *ngFor="#detected of recentDetections">
<td>{{detected.broadcastId}}</td>
<td>{{detected.vendor}}</td>
<td>{{detected.timeStamp | date:'yyyy-MM-dd HH:mm:ss'}}</td>
<td>{{detected.macAddress}}</td>
</tr>
</tbody>
</table>
</div>
D'après les résultats de console.log(this.recentDetections[0].macAddress)
, l'objet recentDetections est en cours de mise à jour, mais le tableau de 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 de la zone angulaire. Cela interrompt la détection des changements. Cela devrait fonctionner:
import {Component, OnInit, NgZone} from 'angular2/core';
export class RecentDetectionComponent implements OnInit {
recentDetections: Array<RecentDetection>;
constructor(private zone:NgZone, // <== added
private recentDetectionService: RecentDetectionService) {
this.recentDetections = new Array<RecentDetection>();
}
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 de changement, voir Déclencher la détection de changement manuellement dans Angular
Il existe d'autres moyens d'invoquer la détection de changement.
ChangeDetectorRef.detectChanges()
pour exécuter immédiatement la détection de changement pour le composant actuel et ses enfants
ChangeDetectorRef.markForCheck()
d'inclure le composant actuel la prochaine fois que la détection de changement angulaire est exécutée
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 souhaite le placer ici en tant que solution testée et fonctionnant à l'aide de ChangeDetectorRef , je vois un bon point ici:
Une autre solution consiste à injecter
ChangeDetectorRef
et à appelercdRef.detectChanges()
au lieu dezone.run()
. Cela pourrait être plus efficace, car il n’exécutera pas de détection de changement sur l’ensemble arborescence des composants commezone.run()
fait. - Mark Rajcok
Donc, le code doit être comme:
import {Component, OnInit, ChangeDetectorRef} from 'angular2/core';
export class RecentDetectionComponent implements OnInit {
recentDetections: Array<RecentDetection>;
constructor(private cdRef: ChangeDetectorRef, // <== added
private recentDetectionService: RecentDetectionService) {
this.recentDetections = new Array<RecentDetection>();
}
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()
dans subscibe pourrait poser problème Tentative d'utilisation d'une vue détruite: detectChanges
Pour le résoudre, vous devez unsubscribe
avant de détruire le composant, ainsi le code complet ressemblera à ceci:
import {Component, OnInit, ChangeDetectorRef, OnDestroy} from 'angular2/core';
export class RecentDetectionComponent implements OnInit, OnDestroy {
recentDetections: Array<RecentDetection>;
private timerObserver: Subscription;
constructor(private cdRef: ChangeDetectorRef, // <== added
private recentDetectionService: RecentDetectionService) {
this.recentDetections = new Array<RecentDetection>();
}
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<RecentDetection>;
EDIT: Si @Input()
est important, c'est parce 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 décorateur @Input()
ou @Output()
est modifié, un événement ngOnChanges
- se déclenchera et la vue se mettra à jour avec la nouvelle valeur. Vous pouvez dire que @Input()
liera la valeur dans les deux sens.
rechercher Entrée sur ce lien par angulaire: glossaire pour plus d'informations
EDIT: après en savoir plus sur le développement de Angular 2, je me suis dit que faire un @Input()
n'était vraiment pas la solution, et comme mentionné dans les commentaires,
@Input()
s'applique uniquement lorsque les données sont modifiées par liaison de données à partir de en dehors du composant (les données liées dans le parent ont été modifiées) et non lorsque le 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 garderai toujours 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'avais oublié d'utiliser @ViewChild (NameOfMyChieldComponent). J'ai perdu au moins 3 heures juste pour cette erreur stupide. i.e: Je n'ai pas eu besoin d'utiliser aucune de ces méthodes:
Au lieu de gérer les zones et de détecter les modifications, laissez AsyncPipe gérer la complexité. Cela mettra les abonnements observables, les désabonnements (pour éviter les fuites de mémoire) et la détection des modifications sur les épaules angulaires.
Changez votre classe pour faire un observable, qui émettra les résultats de nouvelles demandes:
export class RecentDetectionComponent implements OnInit {
recentDetections$: Observable<Array<RecentDetection>>;
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:
<tr *ngFor="let detected of recentDetections$ | async">
...
</tr>
Voulez-vous ajouter qu'il est préférable de créer un service avec une méthode qui prendra l'argument interval
et:
exhaustMap
comme dans le code ci-dessus);