If you are working with Angular, you might have encountered the dreaded ExpressionChangedAfterItHasBeenCheckedError. This error occurs when the value of a property or variable changes after the change detection cycle has completed, and Angular tries to update the view with the new value. This can cause inconsistencies and unexpected behavior in your application.
I will explain what causes this error, how to avoid it, and how to fix it if you encounter it. I will also share some tips and best practices for writing clean and stable code with Angular.
What causes ExpressionChangedAfterItHasBeenCheckedError?
The root cause of this error is that Angular runs change detection twice in development mode, to ensure that no data bindings have changed since the previous check. This is a helpful feature that can catch bugs and errors early on, but it also means that any changes that happen between the first and second check will trigger the error.
There are two common scenarios where this can happen:
- When a child component changes a property or variable that is bound to an input of a parent component. This can create a feedback loop where the parent component updates its view based on the new input value, which in turn triggers another change detection cycle, which detects the change again and throws the error.
- When a component uses a service or a directive that changes a property or variable asynchronously, such as with an HTTP request or a setTimeout function. This can create a race condition where the change detection cycle finishes before the asynchronous operation completes, and then tries to update the view with the new value after it has already been checked.
How to avoid ExpressionChangedAfterItHasBeenCheckedError?
The best way to avoid this error is to follow some simple rules and best practices when writing your Angular code:
- Use immutable data structures and avoid mutating your inputs. This will prevent any unwanted side effects and make your code easier to reason about and test.
- Use the OnPush change detection strategy for your components. This will tell Angular to only run change detection when the inputs or outputs of your component change, or when you explicitly mark your component as dirty with the ChangeDetectorRef service. This will improve your performance and reduce the chances of triggering the error.
- Use observables and async pipes for handling asynchronous data. This will let Angular handle the subscription and unsubscription of your data streams, and automatically update your view when new data arrives. This will also make your code more declarative and reactive.
- Use lifecycle hooks and setters for handling input changes. If you need to perform some logic or side effects when an input changes, you can use the ngOnChanges lifecycle hook or a setter method for your input property. This will ensure that your logic runs before the view is updated, and avoid any inconsistencies.
How to fix ExpressionChangedAfterItHasBeenCheckedError?
If you still encounter this error after following the above tips, there are some ways to fix it depending on your situation:
- If the error is caused by a child component changing an input of a parent component, you can use the AfterViewInit or AfterViewChecked lifecycle hooks to run your logic after the view has been initialized or checked. This will ensure that your logic runs after the second change detection cycle, and avoid the error.
- If the error is caused by an asynchronous operation changing a property or variable, you can use the setTimeout function to defer your logic until the next tick of the event loop. This will ensure that your logic runs after the current change detection cycle, and avoid the error.
- If none of the above solutions work for you, you can use the ChangeDetectorRef service to manually detach and reattach your component from the change detection tree. This will give you full control over when and how your component is checked, but it also comes with some risks and drawbacks. You should only use this as a last resort, and be careful not to introduce any memory leaks or performance issues.