Hey I have create a demo to demonstrate a problem in a large scale.
Update: I have updated the demo, the problem is with ngIf!!
This the demo: https://stackblitz.com/edit/angular-ivy-bac4pe?file=src%2Fapp%2Flist%2Flist.component.ts
And this is the problem: we used directives not only as extension for behavior of components , but also as external apis for components. We have a list component which suppose to show the current items of the list with pagination. We have a paginator directive which his job is to expose Pagination Api for the component. We have a pagination Service which perform the actual pagination(paginate method) according to data and page size.
I have occurred a very weird behavior: There is a setInterval which act like a polling - update data every 5 seconds. When I try to update current list of items through directive->Service-> The items doesn't get updated, even though I have called markforCheck ,because I working on Push Strategy to increase performance. I don't want to call detectChanges because it is a bad practice.
When I try to update current list of items through Service->Component with markForCheck everything works just fine.
I will be happy if someone can explain this phenomena in detailed why this happens and how to solve this issue.
what happened is a sequence of causes.
first, note that this.cdr.markForCheck()
will not run change detection, but mark its ancestors as needing to run change detection. Next time change detection runs anywhere, it will run also for those components which were marked.
The second and more important is the *ngIf
structure. when changes happened *ngIf
will notify first and do the following: (github)
@Input()
set ngIf(condition: T) {
this._context.$implicit = this._context.ngIf = condition;
this._updateView();
}
// ...
private _updateView() {
if (this._context.$implicit) {
if (!this._thenViewRef) {
this._viewContainer.clear();
this._elseViewRef = null;
if (this._thenTemplateRef) {
this._thenViewRef =
this._viewContainer.createEmbeddedView(this._thenTemplateRef, this._context);
}
}
} else {
if (!this._elseViewRef) {
this._viewContainer.clear();
this._thenViewRef = null;
if (this._elseTemplateRef) {
this._elseViewRef =
this._viewContainer.createEmbeddedView(this._elseTemplateRef, this._context);
}
}
}
}
as you can see:
this._viewContainer.createEmbeddedView(this._thenTemplateRef, this._context);
which means it takes the dom and regenerates it! during this process, you will lose the changed data to be rendered.
the next time that parent component data changes and its change detection triggered also check for changes in child component because you reserved this check by calling markForCheck()
in the previous time, again *ngIf
react to changes before its container, so it gets and replaces dom with the old data. and it goes on like this ... so you always are one step back.