It might be that the code in your service somehow breaks out of Angular's zone. This breaks change detection. This should work:
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());
}
}
For other ways to invoke change detection see Triggering change detection manually in Angular
Alternative ways to invoke change detection are
ChangeDetectorRef.detectChanges()
to immediately run change detection for the current component and its children
ChangeDetectorRef.markForCheck()
to include the current component the next time Angular runs change detection
ApplicationRef.tick()
to run change detection for the whole application
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 callcdRef.detectChanges()
instead ofzone.run()
. This could be more efficient, since it will not run change detection over the entire component tree likezone.run()
does. – Mark Rajcok
So code must be like:
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:
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<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();
}
}
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:
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<Array<RecentDetection>>;
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:
<tr *ngFor="let detected of recentDetections$ | async">
...
</tr>
Want to add, that it's better to make a service with a method that will take interval
argument, and:
exhaustMap
like in code above);If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With