Here you go two solutions!
1. Modify ChangeDetectionStrategy to OnPush
For this solution, you're basically telling angular:
Stop checking for changes; I'll do it only when I know is necessary
Modify your component so it'll use ChangeDetectionStrategy.OnPush
like this:
@Component({
selector: 'app-child',
templateUrl: './child.component.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ChildComponent implements OnInit {
// ...
}
With this, things don’t seem to work anymore. That's because from now on you'll have to make Angular call the detectChanges()
manually.
this.cdr.detectChanges();
If you're interested, please check this article. It helped me understand how ChangeDetectionStrategy
works.
2. Understanding ExpressionChangedAfterItHasBeenCheckedError
Please check this video about the issue (is great!). Also, here is a small extract from this article about the causes for this error, I've tried to include only the parts that helped me to understand this.
The full article shows real code examples about every point shown here.
The root cause is angular lifecycle's:
After each operation Angular remembers what values it used to perform
an operation. They are stored in the oldValues property of the
component view.
After the checks have been done for all components Angular then starts
the next digest cycle but instead of performing operations it compares the current values with the ones it remembers from
the previous digest cycle.
The following operations are being checked at digest cycles:
check that values passed down to the child components are the same as
the values that would be used to update properties of these components
now.
check that values used to update the DOM elements are the same as
the values that would be used to update these elements now perform the
same.
checks for all child components
And so, the error is thrown when the compared values are different., blogger Max Koretskyi stated:
The culprit is always the child component or a directive.
And finally here are some real-world samples that usually cause this error:
- Shared services (example)
- Synchronous event broadcasting (example)
- Dynamic component instantiation (example)
In my case, the problem was a dynamic component instantiation.
Also, from my own experience, I strongly recommend everyone to avoid the setTimeout
solution, in my case caused an "almost" infinite loop (21 calls which I'm not willing to show you how to provoke them),
I would recommend always keeping in mind the Angular life cycle's so you can take into account how they would be affected every time you modify another component's value. With this error Angular is telling you:
You're maybe doing this the wrong way, are you sure you're right?
The same blog also says:
Often, the fix is to use the right change detection hook to create a dynamic component
A short guide for me is to consider at least the following things while coding:
(I'll try to complement it over time):
- Avoid modifying parent component values from its child's components,
instead: modify them from their parent.
- When you use
@Input
and @Output
directives try to avoid triggering lifecycle changes unless the component is completely initialized.
- Avoid unnecessary calls of
this.cdr.detectChanges();
they can trigger more errors, especially when you're dealing with a lot of dynamic data
- When the use of
this.cdr.detectChanges();
is mandatory make sure that the variables (@Input, @Output, etc
) being used are filled/initialized at the right detection hook (OnInit, OnChanges, AfterView, etc
)
- When possible, remove rather than hide, this is related to point 3 and 4. (same quote for angulardart)
- Avoid any kind of logic inside
setters
annotated with @Input
, setters are executed previously to ngAfterViewInit
so it'll easily trigger the issue. In case you need to, its better of to put that logic inside the ngOnChanges
method.
Also
If you want to fully understand Angular Life Hook I recommend you to read the official documentation here:
https://angular.io/guide/lifecycle-hooks