I have two components, where I use ChangeDetectionStrategy.OnPush. A parent component:
@Component({
changeStaregy: ChangeDetectionStrategy.OnPush,
template:`
<button (click)="onClick()">clear</button>
<div>
<test [model]="model"></test>
</div>`
})
export class AppComponent {
model: TestModel;
constructor(){
this.model = { id: 1, text: 'bla bla bla'}
}
onClick() {
this.model = new TestModel();
}
}
and a child component that just displays a data:
@Component({
changeStrategy: ChangeDetectionStrategy.OnPush,
selector: 'test',
template: `
<div>
<div> {{model.id}} </div>
<div> {{model.text}} </div>
</div>`
})
export class TestComponent {
@Input() model: TestModel;
}
When I click on the button "clear", it calls onClick() function, which assigns an empty entity to "model". This triggers a change detection, because the input was changed (OnPush strategy). But if I wrap assignment with an async call, the change detection doesn't work and therefore UI is not updated:
onClick() {
setTimeout(() => {
this.model = new TestModel();
}, 2000);
}
Angular2+ has NgZone which patches a setTimeout function. The patched setTimeout must trigger the change detection, but in my case it doesn't. Why the change detection doen't work? How can I fix it?
Because the changeDetectionStrategy
is set to OnPush
on the parent, the change detection cycle stops at the parent element. As a result any child of this parent will have the strategy set to OnPush
, regardless of its own setting.
No @Input
of the parent changes, and therefor the change detector doesn't go in any deeper towards the childs of the parent. You should execute a detectChanges
to have the changes take effect
Here are the steps as to what is happening in your scenario:
onClick()
method is executed.onClick()
method executes, Angular calls ApplicationRef.tick()
to kick off change detection. Because the ancestor components were marked as dirty, change detection happens on your OnPush components. Keep in mind that at this point, the setTimeout callback hasn't executed yet.@Input
change (because AppComponent has no Inputs), and component and its ancestors aren't marked as dirty, Change detection won't go through to the child component. So the change to this.model
won't be reflected in the DOM.I found this helpful: https://www.mokkapps.de/blog/the-last-guide-for-angular-change-detection-you-will-ever-need
When using OnPush change detection strategy, change detection only runs when an input property changes, bound event is trigger from component or you manually detect changes.
In your example plunker the child component changes input by itself in changeSetTimeout
and changeModelInZone
.
When you click on the button second time bounded event is triggered due to which template is updated.
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