However, when I use ChangeDetectionStrategy.OnPush, no matter what I try, I cannot get the value in ComponentA to update, even from the root component, where I tried this:
A component has an associated view. The view references the DOM and is what we want to be updated. When you use OnPush
the view of a component needs to be marked as dirty if the component's state changes externally.
When you say even from the root component
it means you're trying to mark the wrong view as dirty. If you want to see changes in ComponentA
then you need to flag that component view as dirty.
Something like this.
@Component({...})
public class ComponentA implements OnInit {
public count; // rendered in the view
public constructor(private _change: ChangeDetectorRef,
private _service: MyService) {
}
public onInit() {
this._service.getCounter().subscribe(value=> {
this.count = value; // will not update the view.
this._change.markForCheck(); // tell Angular it's dirty
});
}
}
So the above will work in 99% of the cases, but if the getCounter()
methods returns an observable that executes outside the scope of Angular, and you have to do this explicitly because async operations are automatically zoned, then you have to use the zone.run()
method. Otherwise, even if you mark the view dirty. Angular isn't going to check if any views need to be updated. This should not happen unless you're using non-Angular events or have explicitly run outside of Angular.
The alternative is to use the async
pipe, and is the easier approach.
@Component({
template: `<span>{{count$ | async}}</span>`
})
public class ComponentA implement OnInit {
public count$: Observable<number>;
public constructor(private _service: MyService) {
}
public onInit() {
this.count$ = this._service.getCounter();
}
}
The async
pipe uses a reference to ChangeDetectorRef
will also mark the view as dirty for you. So it saves you from a lot of boilerplate code.
The real world scenario is that there's a lot of components like ComponentA all rendering translated values, and when the selected language changes, I need all these translated values to update accordingly. I don't want to build a listener into each individual component and call detectChanges from there
Then you best bet is to use the async
pipe and make your components reactive.
If we're talking about something large scale and effects a lot of components, then maybe this root component should pass the value down to components as an @Input()
which will also trigger them to be rendered. While this creates a coupling between all of the components it says you from having to worry about updating the views.
ComponentB
s extend it? If not, you could useinput
s to those components, which will trigger an onPush change. – Cherokee