"Expression has changed after it was checked" — why does it happen and how to deal with it?
\n\nIf you have ever written an Angular application, you have probably encountered this ominous message in the console:
\n\nERROR Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'false'. Current value: 'true'.\n\nThis error is a real "scourge" for developers. Thousands of questions on Stack Overflow, hundreds of hours of debugging, dozens of nervous breakdowns. But the good news: it can not only be fixed, but also understood why it occurs. In this article, we will break down the top causes of the Expression has changed after it was checked error, write examples in TypeScript, and provide specific solutions. Let's go!
\n\nWhat does this error actually mean?
\n\nAngular works in two modes: development (dev mode) and production. In dev mode, the framework performs an additional check — after each change detection cycle, it runs a second pass to ensure that the data hasn't changed "on the fly." If the value changed between the first and second pass — Angular throws the ExpressionChangedAfterItHasBeenCheckedError error.
\n\nIn simple terms: you told Angular "here is the data," it checked it, and then you changed it. Angular gets upset and yells.
\n\nTop 3 causes of the error
\n\n1. Changing data in lifecycle hooks after ngAfterViewInit
\n\nThe most popular cause is trying to change a component property in the ngAfterViewInit, ngAfterContentInit, or ngAfterViewChecked hook. At this point, Angular has already finished checking the view, and any change causes an error.
TypeScript example:
\n\nimport { Component, AfterViewInit } from '@angular/core';\n\n@Component({\n selector: 'app-example',\n template: '<p>{{ message }}</p>'\n})\nexport class ExampleComponent implements AfterViewInit {\n message = 'Initial value';\n\n ngAfterViewInit(): void {\n // Error! Changing the property after the view has been checked\n this.message = 'Changed value';\n }\n}\n\nHere, Angular first checks message (value "Initial value"), then calls ngAfterViewInit, where you change it to "Changed value." On the second check, Angular sees the discrepancy and throws an error.
2. Asynchronous operations inside the lifecycle
\n\nIf you run asynchronous code (setTimeout, Promise, Observable) inside a hook, the data update happens outside Angular's zone of control.
\n\nExample:
\n\nimport { Component, OnInit } from '@angular/core';\n\n@Component({\n selector: 'app-async',\n template: '<p>{{ data }}</p>'\n})\nexport class AsyncComponent implements OnInit {\n data = 'Start';\n\n ngOnInit(): void {\n setTimeout(() => {\n this.data = 'New value';\n }, 0);\n }\n}\n\nAlthough setTimeout with a 0 ms delay executes almost immediately, Angular has already finished its check. In dev mode, this will lead to an error.
\n\n3. Nested components and passing data via Input
\n\nWhen a parent component passes data to a child component via @Input, and the child component tries to change this data in its own lifecycle, a conflict arises.
Example:
\n\n// Parent component\n@Component({\n selector: 'app-parent',\n template: '<app-child [value]="parentValue"></app-child>'\n})\nexport class ParentComponent {\n parentValue = 'Original';\n}\n\n// Child component\n@Component({\n selector: 'app-child',\n template: '<p>{{ value }}</p>'\n})\nexport class ChildComponent implements AfterViewInit {\n @Input() value: string = '';\n\n ngAfterViewInit(): void {\n this.value = 'Changed in