Angular 2: la vista no se actualiza después de los cambios del modelo

Tengo un componente simple que llama a una APLICACIÓN REST cada pocos segundos y recibe datos JSON. Puedo ver en mis informes de registro y en el tráfico de la red que los datos JSON que se están devolviendo están cambiando, y mi modelo se está actualizando; sin embargo, la vista no está cambiando.

Mi componente se ve así:

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

Y mi vista se ve así:

 

Recently detected

Recently detected devices

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

Puedo ver a partir de los resultados de console.log(this.recentDetections[0].macAddress) que el objeto recientesDetections se está actualizando, pero la tabla en la vista nunca cambia a menos que vuelva a cargar la página.

Estoy luchando para ver qué estoy haciendo mal aquí. ¿Alguien puede ayudar?

Es posible que el código en su servicio salga de la zona de Angular. Esto rompe la detección de cambios. Esto debería funcionar:

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

Para conocer otras formas de invocar la detección de cambios, consulte Desencadenar detección de cambios manualmente en Angular

Las formas alternativas de invocar la detección de cambios son

 ChangeDetectorRef.detectChanges() 

para ejecutar inmediatamente la detección de cambios para el componente actual y sus hijos

 ChangeDetectorRef.markForCheck() 

para incluir el componente actual la próxima vez que Angular ejecute la detección de cambio

 ApplicationRef.tick() 

ejecutar la detección de cambios para toda la aplicación

Originalmente es una respuesta en los comentarios de @Mark Rajcok, pero quiero ubicarlo aquí como una prueba y funcionó como una solución usando ChangeDetectorRef . Veo un buen punto aquí:

Otra alternativa es inyectar ChangeDetectorRef y llamar a cdRef.detectChanges() lugar de zone.run() . Esto podría ser más eficiente, ya que no ejecutará la detección de cambios en todo el árbol de componentes como zone.run() . – Mark Rajcok

Entonces el código debe ser como:

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

Editar : Usar .detectChanges() dentro de la subscibe podría generar problemas . Intentar usar una vista destruida: detectarCambios

Para resolverlo, debe unsubscribe de unsubscribe antes de destruir el componente, por lo que el código completo será como sigue:

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

Intente utilizar @Input() recentDetections: Array;

EDITAR: El motivo por el cual @Input() es importante es porque desea vincular el valor en el archivo de tipo typescript / javascript a la vista (html). La vista se actualizará si se modifica un valor declarado con el decorador @Input() . Si se @Input() un @Input() o @Output() , se ngOnChanges un ngOnChanges ngOnChanges, y la vista se actualizará con el nuevo valor. Puede decir que @Input() vinculará el valor en dos sentidos.

buscar Entrada en este enlace por angular: glosario para más información

EDIT: después de aprender más sobre el desarrollo de Angular 2, pensé que hacer un @Input() realmente no es la solución, y como se menciona en los comentarios,

@Input() solo se aplica cuando los datos se modifican mediante el enlace de datos desde fuera del componente (los datos vinculados en el elemento principal modificado) no se modifican cuando se cambian los datos del código dentro del componente.

Si tiene una respuesta de @Günter, es una solución más precisa y correcta para el problema. Aún conservaré esta respuesta aquí, pero siga la respuesta de Günter como la correcta.

En mi caso, tuve un problema muy similar. Estaba actualizando mi vista dentro de una función que estaba siendo llamada por un componente principal, y en mi componente principal olvidé usar @ViewChild (NameOfMyChieldComponent). Perdí al menos 3 horas solo por este estúpido error. es decir: no necesitaba usar ninguno de esos métodos:

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

En lugar de ocuparse de las zonas y cambiar la detección, deje que AsyncPipe maneje la complejidad. Esto pondrá la subscripción observable, la cancelación (para evitar memory leaks) y la detección de cambios en los hombros angulares.

Cambia tu clase para hacer un observable, que emitirá resultados de nuevas solicitudes:

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

Y actualice su vista para usar AsyncPipe:

  ...  

Desea agregar, que es mejor hacer un servicio con un método que tome un argumento de interval , y:

  • crear nuevas solicitudes (utilizando exhaustMap como en el código anterior);
  • manejar solicitudes de errores;
  • evitar que el navegador realice nuevas solicitudes sin conexión.