Angular 2 - View not updating after model changes

匿名 (未验证) 提交于 2019-12-03 01:48:02

问题:

I have a simple component which calls a REST api every few seconds and receives back some JSON data. I can see from my log statements and the network traffic that the JSON data being returned is changing, and my model is being updated, however, the view isn't changing.

My component looks like:

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

And my view looks like:

Recently detected

Recently detected devices

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

I can see from the results of console.log(this.recentDetections[0].macAddress) that the recentDetections object is being updated, but the table in the view never changes unless I reload the page.

I'm struggling to see what I'm doing wrong here. Can anyone help?

回答1:

It might be that the code in your service somehow breaks out of Angulars zone. This breaks change detection. This should work:

import {Component, OnInit, NgZone} from 'angular2/core';  export class RecentDetectionComponent implements OnInit {      recentDetections: Array;      constructor(private zone:NgZone, // ();     }      getRecentDetections(): void {         this.recentDetectionService.getJsonFromApi()             .subscribe(recent => {                   this.zone.run(() => { //  this.getRecentDetections());     } } 

For other ways to invoke change detection see Triggering Angular2 change detection manually

Alternative ways to invoke change detection are

ChangeDetectorRef.detectChanges() 

to immedialtely run change detection for the current component and it's childrend

ChangeDetectorRef.markForCheck() 

to include the current component the next time Angular runs change detection

ApplicationRef.tick() 

to run change detection for the whole application



回答2:

Try to use @Input() recentDetections: Array;

EDIT: The reason why @Input() is important is because you want to bind the value in the typescript/javascript file to the view (html). The view will update itself if a value declared with the @Input() decorator is changed. If an @Input() or @Output() decorator is changed, an ngOnChanges-event will trigger, and the view will update itself with the new value. You may say that the @Input() will two-way bind the value.

search for Input on this link by angular: glossary for more information

EDIT: after learning more about Angular 2 development, I figured that doing an @Input() really is not the solution, and as mentioned in the comments,

@Input() only applies when the data is changed by data-binding from outside the component (bound data in the parent changed) not when the data is changed from code within the component.

If you have a look @Günter's answer it is a more accurate and correct solution to the problem. I will still keep this answer here, but please follow Günter's answer as the correct one.



回答3:

It is originally an answer in the comments from @Mark Rajcok, But I want to place it here as a tested and worked as a solution using ChangeDetectorRef , I see a good point here:

Another alternative is to inject ChangeDetectorRef and call cdRef.detectChanges() instead of zone.run(). This could be more efficient, since it will not run change detection over the entire component tree like zone.run()

So code must be like:

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

Edit: Using .detectChanges() inside subscibe could lead to issue Attempt to use a destroyed view: detectChanges

To solve it you need to unsubscribe before you destroy the component, so the full code will be like:

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


回答4:

In my case, I had a very similar problem. I was updating my view inside a function that was being called by a parent component, and in my parent component I forgot to use @ViewChild(NameOfMyChieldComponent). I lost at least 3 hours just for this stupid mistake. i.e: I didn't need to use any of those methods:

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


回答5:

Instead of dealing with zones and change detection ― let AsyncPipe handle complexity. This will put observable subscription, unsubscription (to prevent memory leaks) and changes detection on Angular shoulders.

Change your class to make an observable, that will emit results of new requests:

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

And update your view to use AsyncPipe:

     ... 

Want to add, that it's better to make service method take interval argument, and make it:

  • new requests (by using exhaustMap like in code above);
  • handle requests errors;
  • stop browser from making new requests while offline.


标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!