166 votes

Angular 2 - La vue ne se met pas à 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 en retour certaines données JSON. Je peux le voir dans mon journal consolidés et le trafic réseau que les données JSON retourné est en train de changer, et mon modèle est en cours de mise à jour, cependant, le point de vue n'est pas en train de changer.

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 de mon point de 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>

Je peux le voir à partir des résultats de l' console.log(this.recentDetections[0].macAddress) que le recentDetections objet est mis a jour, mais la table dans la vue ne change jamais, sauf si je recharge la page.

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

248voto

Günter Zöchbauer Points 21340

Il se pourrait que le code dans votre service, en quelque sorte éclate Angulaire de la zone. Cela rompt avec la détection de changement. 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 les autres moyens de faire des changements de détection de voir le Déclenchement de la détection de changement manuellement dans Angulaire

D'autres façons d'invoquer la détection de changement sont

ChangeDetectorRef.detectChanges()

pour exécuter immédiatement la détection de changement du composant en cours et de ses enfants

ChangeDetectorRef.markForCheck()

pour inclure le composant en cours, la prochaine fois Angulaire pistes de détection de changement de

ApplicationRef.tick()

pour exécuter la détection de changement pour l'ensemble de l'application

46voto

Al-Mothafar Points 3874

Il est à l'origine une réponse dans les commentaires de @Mark Rajcok, Mais je veux placer ici comme un a testé et a travaillé comme une solution à l'aide de ChangeDetectorRef , je vois un bon point ici:

Une autre alternative consiste à injecter ChangeDetectorRef et appel cdRef.detectChanges() au lieu de zone.run(). Cela pourrait être plus efficace, car il ne sera pas exécuté à la détection de changement sur l'ensemble de la composant arbre comme zone.run() . Marque Rajcok

Donc le code doit être:

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'aide de .detectChanges() à l'intérieur de subscibe pourrait conduire à émettre de la Tentative d'utilisation d'un détruits vue: detectChanges

Pour le résoudre, vous devez unsubscribe avant de détruire le composant, de sorte que le code complet sera comme:

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

}

8voto

John Points 2291

Essayez d'utiliser @Input() recentDetections: Array<RecentDetection>;

EDIT: La raison pourquoi @Input() est important, c'est parce que vous voulez lier la valeur dans le fichier d'enregistrement/fichier javascript pour la visualiser (html). La vue est mise à jour automatiquement si une valeur déclarée avec l' @Input() décorateur est changé. Si un @Input() ou @Output() décorateur est modifié, une ngOnChanges-événement déclencheur, et l'affichage se mettra à jour avec la nouvelle valeur. On peut dire que l' @Input() deux-de manière à lier la valeur.

recherche pour Entrée sur ce lien angulaire: glossaire pour plus d'informations

EDIT: après en avoir appris plus sur Angulaire 2 de développement, j'ai pensé que faire un @Input() n'est 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 (données lié à la mère changé) pas lorsque le les données sont modifiées à partir du code au sein de la composante.

Si vous avez un look @Günter de répondre, il est plus précise et correcte de la solution au problème. Je vais quand même garder cette réponse ici, mais s'il vous plaît suivez Günter réponse comme correcte.

2voto

user3674783 Points 21

Dans mon cas, j'ai eu un problème très similaire. J'étais en train de mettre à 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. c'est-à-dire: je n'ai eu besoin d' aucune de ces méthodes:

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

1voto

glukki Points 1443

Au lieu de traiter avec des zones et de la détection de changement - laissez AsyncPipe à gérer la complexité. Cela permettra de mettre observables abonnement, désabonnement (pour éviter les fuites de mémoire) et de détection des changements sur Angulaire épaules.

Changer votre classe pour faire une observable, qui émettent des 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 de mettre à jour votre point de vue à utiliser AsyncPipe:

<tr *ngFor="let detected of recentDetections$ | async">
    ...
</tr>

Souhaitez ajouter, que c'est mieux de faire un service avec une méthode qui prendra interval d'argument, et:

  • créer de nouvelles demandes (à l'aide d' exhaustMap comme dans le code ci-dessus);
  • traiter les demandes des erreurs;
  • arrêter navigateur de faire de nouvelles demandes en mode hors connexion.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X