Angular ngOnChanges detectChanges happens before nested data bindings are updated

Viewed 552

I'm running into a problem making my UI update in a situation where I share data between a parent directive and child components. An @Input() binding exists on the parent, and the data is then internally passed to the child components on ngOnChanges using @ContentChildren(). This way I don't have to bind my data to the parent and all child components every time I use the component.

To limit the amount of change detection cycles on the inner components, I am using the ChangeDetectionStrategy.OnPush strategy. I then trigger my own change detection when the data is passed from the parent to the children. This works great, except the change detection is now called before other data changes are processed deeper in the hierarchy. This leads to stale data that's always behind a cycle.

Here's a StackBlitz which roughly represents my current situation:
https://stackblitz.com/edit/angular-ivy-2tszsu
When updating the data, you'll see that with the OnPush strategy, the Template value is always the previous Data value.
PS. In this example data and templateData are one and the same for simplicity, but in my use case there are various unique properties.

From my understanding, here's what's happening:

  1. @Input() data on ParentComponent changes.
  2. ngOnChanges on ParentComponent fires and passes new data to ChildPushComponent using my function setData(data).
  3. ChildPushComponent fires change detection inside setData(data). This later makes the UI update with new the data and old templateData.
  4. @Input() templateData on TemplateDirective changes.
    These are ignored because the ChildPushComponent has already detected changes beforehand.

I'm looking for a solution to make the template value visually update at the same time as the other values (i.e. when I run the change detection manually inside ChildPushComponent). Preferably without setTimeout trickery and without removing the OnPush strategy. Any help is much appreciated.

2 Answers

In your child-push.component.html simply replace this._cd.detectChanges() with this._cd.markForCheck(); that should do it.

For difference between detectChanges() and markForCheck() check out this answer

If I may take the problem in an other point of view,

I would pass data through the input

<app-child-push [data]="data">...</app-child-push>

And then, if you which to do something when the data changes, then add the ngOnChanges detection directly within the child-push.component.ts.

That way you can keep your changeDetection

There is your starckblitz with the new code : https://stackblitz.com/edit/angular-ivy-g18nwc?file=src/app/child/child-push.component.ts.

I'm not sure I understand your use of the directive here, so let me know if I misunderstood your question and I'll remove my answer.

Related