4

There is such an error:

ERROR Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'ngTemplateOutlet: undefined'. Current value: 'ngTemplateOutlet: [object Object]'.

at viewDebugError (core.js:9775)

at expressionChangedAfterItHasBeenCheckedError (core.js:9753)

at checkBindingNoChanges (core.js:9920)

at checkNoChangesNodeInline (core.js:13970)

at checkNoChangesNode (core.js:13942)

at debugCheckNoChangesNode (core.js:14771)

at debugCheckDirectivesFn (core.js:14673)

at Object.eval [as updateDirectives] (ShowEventComponent.html:73)

at Object.debugUpdateDirectives [as updateDirectives] (core.js:14655)

at checkNoChangesView (core.js:13780)

She appeared because of this:

<tr *ngFor="let user of users">
    <ng-template [ngTemplateOutlet]="loadTemplate(user)" 
                [ngTemplateOutletContext]="{ $implicit: user}">
    </ng-template>
</tr>

How can I fix it?

I use Angular 5.2.0, rxjs 5.5.6

user10031243
  • 49
  • 1
  • 2
  • 1
    Nice article about this issue: https://blog.angularindepth.com/everything-you-need-to-know-about-the-expressionchangedafterithasbeencheckederror-error-e3fd9ce7dbb4 – Envil Jul 04 '18 at 10:08
  • Another similar SO question: https://stackoverflow.com/questions/39787038/how-to-manage-angular2-expression-has-changed-after-it-was-checked-exception-w – Envil Jul 04 '18 at 10:08
  • None of this helped.( – user10031243 Jul 04 '18 at 10:31
  • this type of issue is not easy to fix, you have to understand how angular change detection mechanism work, then from there you apply your knowledge on to your project and find out the key issue. – Envil Jul 04 '18 at 10:34
  • Does this answer your question? [How to manage Angular2 "expression has changed after it was checked" exception when a component property depends on current datetime](https://stackoverflow.com/questions/39787038/how-to-manage-angular2-expression-has-changed-after-it-was-checked-exception-w) – luiscla27 Jan 12 '22 at 18:21

5 Answers5

8

This has to do with change detection. You can read more about it here. I solved this problem by using the detectChanges() method from ChangeDetectorRef i.e.

constructor(private cdr: ChangeDetectorRef) {}

  ngOnInit() {}

  ngAfterViewInit(): void {
    this.cdr.detectChanges();
  }

You'll need to implement the AfterViewInit interface.

5

I recently fixed a similar issue and I could not find much help so I am going to offer some help.

First off, this only occurs in dev mode but it is a serious issue. When Angular runs change detection it keeps track of all of its bound values (such as the ngTemplateOutlet) and then compares that value to after change detection cycle ends.

The reason you are getting this error is because you have a method in the html setting the [ngTemplateOutlet]. This causes that method (loadTemplate(user)) to run every single time change detection occurs. This is likely 5 or 10 times that the method runs which is not ideal.

The first time through the value is set to undefined and in subsequent cycles the value gets updated. I would guess that the loadTemplate is returning a TemplateRef but when the method runs the first time that TemplateRef is not defined and that is the reason.

There are quite a few ways to fix this and I would need to see your component.ts code. But there are 2 changes that must occur. First you need to set the [ngTemplateOutlet] without using a method. You can use a component Observable or a component field. But secondly you also need to set the field or observable at the right time. You want to delay until the Template is defined. You can do this in an ngOnInit by wrapping the code that sets the field in a setTimeout() or using the lettable rxjs delay(0) if using an Observable.

Again, I would need to see the logic in the loadTemplate(user) and the component.ts to show exactly how to arrange it.

You could also Input the data you need from the parent component to this one and then put the logic using that data after the [ngTemplate]

Vict0ar
  • 51
  • 1
  • 2
2

Like Envil posted you can wrap the content of your loadTemplate() function into setTimeout like:

    setTimeout(() => {
        yourTemplateLoadingMechanism();
    });

but in general I would avoid calling functions in templats for they are called over and over again which makes debugging hard and costs performance. instead you could prefetch all your templates and provide them as usual as two-way-binding or even use rxjs Observables with the async keyowrd which do circumvent your problem even more effective.

conpile
  • 66
  • 2
1

You should set the TemplateRef at AfterContentInit function

duliu1990
  • 359
  • 4
  • 6
1

When in a function loadTemplate(user) , you return TemplateRef, you should get it this way

@ViewChild('readOnlyTemplate', { static: true }) readOnlyTemplate!: TemplateRef<any>

It is important to specify { static: true }

Meteora
  • 11
  • 2