3

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.

8
  • Have you tried to replace if (!data.isFirstChange) { with if (!data.firstChange) {? Commented Aug 6, 2021 at 14:34
  • stackblitz.com/edit/… Commented Aug 6, 2021 at 14:34
  • apparently I didn't succeeded to reproduce the original bug or problem, you can closed this question.@yurzui Commented Aug 6, 2021 at 17:01
  • I have updated the demo, now it reproduce, the problem is with ngIf. Commented Aug 6, 2021 at 17:42
  • not able to reproduce or understand properly Commented Aug 9, 2021 at 7:07

3 Answers 3

5
+100

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.

Sign up to request clarification or add additional context in comments.

2 Comments

You're almost there. *ngIf doesn't replace the dom here it just breaks the order of change detection so that with embedded view Angular doesn't call ngOnChanges hook on components/directives inside embedded view first when processing bindings in its own view
thanks for the answer. @yurzui if you can extend your explanation it will be wonderful, thank in advance!
3

The big difference with *ngIf and the [hidden] tag is, that when using *ngIf the data is not loaded at all. When using the [hidden] tag, the data is loaded but not shown.

It is better to use [hidden] when you want to change the show/hide status frequently.

1 Comment

I have been looking for this answer for so long.
0

The quick answer is use [hidden] instead of *ngIf.

But I still don't know why I need to prefer [hidden] over *ngIf.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.