Reputation: 111
TL;DR: ngOnChanges shows that changes are being detected on an input property, but the view is not being updated
I'm working on an Angular (2+) app trying to make a progress bar for an asynchronous task being fulfilled by a service using an Observable. The Observable pushes numbers as data to indicate what percent of the task has been completed. There are three pieces involved: The Service, the ResultsComponent, and the ProgressBarComponent (child of ResultsComponent) The basic data flow is:
The problem is that the view isn't actually being updated.
In ResultsComponent I have this block:
this.service.doTheTask()
.subscribe(
data => {
this.percentLoaded = data;
this.ref.detectChanges();
},
e => console.log('error:', e),
() => {
this.isLoading = false;
this.percentLoaded = 0;
}
);
Adding ref.detectChanges() successfully makes the OnChanges hook fire in ProgressBarComponent. Component and template are shown below:
Component
export class ProgressBarComponent implements OnChanges {
@Input() percentLoaded = 0;
constructor() { }
ngOnChanges(changes) {
console.log('changes: ', changes);
}
}
Template
<div class="meter">
<div class="percent" [style.width.%]="percentLoaded"></div>
</div>
<p>{{percentLoaded}}% Loaded</p>
As you can see, I'm just logging changes to test. I've verified that ngOnChanges is firing, and that the values are correct. Everything I've read says "Once changes are detected, the view automatically updates" so I don't know what else to do.
Any suggestions?
Upvotes: 4
Views: 6517
Reputation: 21
I had a similar issue and after spending about an hour trying to figure it out I remembered adding change detection strategy onPush to the parent component.
As soon as I removed that the child component started working again. The ngOnChanges method was firing but my view was not updating in the subscribe closure.
Upvotes: 0
Reputation: 4709
After your async task is done you overwrite data that you received from subscription with following line this.percentLoaded = 0;
. Delete it:
() => {
this.isLoading = false;
//delete this line this.percentLoaded = 0;
}
Also your change detection is not triggered, because change detection on inputs is triggered only when reference of object is changed. Number is a simple, not a reference variable, so in your case change detection is not triggered. My suggestion is to subscribe to service in both components and trigger change detection only from child component.
export class ProgressBarComponent {
percentLoaded: number = 0;
constructor(private ref: ChangeDetectorRef, private service: TheService) { }
this.service.doTheTask()
.subscribe(
data => {
this.percentLoaded = data;
this.ref.detectChanges();
},
}
Upvotes: 0