In this post, we will cover in detail an error communication that you'll sometimes come across while erecting Angular operations "Expression has changed after it was checked"
ExpressionChangedAfterItHasBeenCheckedError.
We'll provide a thorough explanation for this error. We'll learn why it occurs, how to remedy it constantly, and how to fix it.
Most of all, we'll explain why this error is useful and how it ties back to the Angular Development Mode. In this post, we will cover the following
We'll first start by snappily remedying the error( a videotape is available for this), and also we will explain the cause and apply the fix. So without further ado, let's get started!
A common script where the error occurs
This type of error generally shows up beyond the original development stages, when we start to have some further expressions in our templates, and we've generally started to use some of the lifecycle hooks like AfterViewInit.
Then's a simple illustration of an element that's formerly throwing this error, taken from this former post
<div class="course">
<div class="spinner-container" *ngIf="dataSource.loading$ | async">
<mat-spinner></mat-spinner>
</div>
<mat-table class="lessons-table mat-elevation-z8" [dataSource]="dataSource">
....
</mat-table>
<mat-paginator [length]="course?.lessonsCount" [pageSize]="3"
[pageSizeOptions]="[3, 5, 10]"></mat-paginator>
</div>
This is a simple element that's displaying an Angular Material Data Table with a paginator, plus a leading index that gets displayed while we stay for the data to load.
Then's what this element looks like when the data is loaded
Material Data Table
And then's what the element looks like when the data is loading
Material Data Table
To find out why the" Expression has changed" error is being thrown in this situation, let's have a look at the simplified interpretation of this element
@Component({
selector: 'course',
templateUrl: './course.component.html'
})
export class CourseComponent implements AfterViewInit {
@ViewChild(MatPaginator) paginator: MatPaginator;
ngAfterViewInit() {
this. paginator. page
.pipe(
start with(null),
tap(() => this.dataSource.loadLessons(...))
).subscribe();
}
}
As we can see, we're using ngAfterViewInit(), because we want to get a reference to the Paginator runner Observable, and the paginator is attained using the@ViewChild() view query.
Whenever the stoner hits the paginator navigation buttons, an event is going to be emitted that triggers the loading of a new data runner, by callingdataSource.loadLessons().
Note that the valve driver is the new pipeable interpretation of the RxJs do driver!
Because the runner Observable doesn't originally emit a value, we're emitting an original value using start with (). This causes the first runner of data to be loaded, else, data would only be loaded if the stoner clicks the paginator.
And then's the Data Source( simplified interpretation)
export class LessonsDataSource implements DataSource<Lesson> {
private loadingSubject = new BehaviorSubject<boolean>(false);
public loading$ = this.loadingSubject.observable();
loadLessons(...) {
this.loadingSubject.next(true);
…Load new data webpage from the backend.
}
}
As we can see, loadLessons() is emitting a new value for the lading$ Observable( it's setting the lading flag to true) and is doing so synchronously, before the asynchronous call to the backend.
Notice that this lading$ Observable is the bone that's getting used in the ngIf expression that shows or hides the leading index.
Error Communication illustration
The law over will throw the following change discovery error
ERROR: Error ExpressionChangedAfterItHasBeenCheckedError Expression has changed after it was checked, former value'( object Object)'. Current value' true'.
at viewDebugError(core.js9515)
at expressionChangedAfterItHasBeenCheckedError(core.js9493)
at checkBindingNoChanges(core.js9662)
This error tells us that there's a problem with an expression in the template, but the question is, which expression? And why does it beget an error?
Remedying "Expression has changed after it was checked"
The debugging process that we will go over below is also done then step by step in this videotape, where we also explain the cause of the error
Then's how we can identify the problematic expression. In the Chrome Dev Tools press, we have a call mound that identifies where exactly the error passed.
Let's click on the link available in the first line of the call mound
at viewDebugError(core.js9515)
This will open the DevTools Javascript Debugger in the line where the error passed let's also add a homemade breakpoint on that line. Still, the breakpoint will hit and we will get the following
If we now reload the element and spark the error again.
Debugging ExpressionChangedAfterItHasBeenCheckedError
The program is now firmed at this point, and we can hang over the variables and go up and down the call mound, to see what's going on.
Notice that line 9515 that's where the error occurs, and the line number with the blue triangle is where we clicked to produce the breakpoint.
We also have a call stack. However, we will see the function call to viewDebugError, If we start clicking on the functions up the call mound.
Relating the former value of the Expression
By pressing the oldValue variable, we can see that the old value of the problematic expression was false, and according to the communication it's now true
Debugging ExpressionChangedAfterItHasBeenCheckedError
Relating the Problematic Expression
But what template expression is causing this error? If we keep clicking up the call mound, we're going to see that a template expression will appear
Debugging ExpressionChangedAfterItHasBeenCheckedError
As we can see, this is the ngIf expression that shows or hides the leading index so this is the problematic template expression!
As we can see, the source maps generated by the Angular CLI are veritably useful to troubleshoot this kind of problem.
The understanding of "Expression has changed after it was checked" Error
This ngIf expression, at first sight, doesn't feel problematic. So why does this throw an error? Then what happens is the ngIf expression over is originally false because the data source isn't loading any data, so loading$ emits false. Still, also the lading index should be hidden as no data is being loaded
If the lading$ Observable last emitted value is false. while Angular is preparing to modernize the View grounded on the rearmost data changes, during that process it calls ngAfterViewInit, which triggers the loading of the first data runner from the backend.
Loading the data would still take a while and it's an asynchronous operation, so the data won't arrive incontinently
Then's the problem as a coetaneous call to dataSource. loadLessons() is made, a new true value of the lading$ flag is emitted incontinently
And it's this new value of the lading flag that accidentally triggers the error!
Let's learn why streamlining this flag during the view construction process is problematic.
The" View Updates Itself" script
The problem then's that we have a situation where the view generation process( which ngAfterViewInit is a part of) is itself further modifying the data that we're trying to display in the first place.
The lading flag starts with false, we tried to display that on the screen, by hiding the leading index. The way that ngAfterViewInit is written causes the presentation of the data to further modify it. After the view is erected, the lading flag is now true. So which value is the lading flag also, true or false? Due to the lack of a decision-making mechanism, Angular preventively throws this error, which only occurs in Development Mode.
Check out this post to learn more about the Angular Development Mode. Let's now look at a solution to this problem as well.
Understanding the result
So then's the result we can not use the paginator. page reference in ngAfterViewInit() and incontinently call the Data Source because that will spark a further revision of the data before Angular could display it on the screen, so it's not clear if the value of the lading flag should be true or false.
To break this issue, we need to let Angular first display the data with the lading flag set to false.
Also, in some unborn time, in a separate Javascript turn, only are we going to call the Data Source loadLessons() system, which will beget the lading flag to be set to true and the lading index will also get displayed.
Original perpetration of the result
To postpone the law inside ngAfterViewInit to another Javascript turn then's one original perpetration that will help us to understand the result more
ngAfterViewInit() {
setTimeout(() => {
this. paginator. page
.pipe(
start with(null),
tap(() => this.dataSource.loadLessons(...))
).subscribe();
});
}
This formerly solves the problem we do not have an error presently!
As we can see, we're using setTimeout() to postpone this law to another Javascript Virtual Machine turn, and notice that we aren't indeed specifying a value for the downtime.
Let's now have a look at an indispensable perpetration with lower law nesting, and also we will explain why this fixes the issue.
A volition using RxJs
This is an indispensable interpretation that looks better due to lower law nesting and uses the RxJs pipeable driver detention
import { start with, tap, delay } from 'rxjs/operators;
ngAfterViewInit() {
this.paginator.page
.pipe(
startWith(null),
delay(0),
tap(() => this.dataSource.loadLessons(...))
).subscribe();
}
How does setTimeout or detention( 0) fix this problem?
Then's why the law above fixes the issue
The original value of the flag is false, so the leading index will NOT be displayed originally
ngAfterViewInit() gets called, but the data source isn't incontinently called, so no variations of the lading index will be made synchronously via ngAfterViewInit()
Angular also finishes rendering the view and reflects the rearmost data changes on the screen, and the Javascript VM turn completes
One moment latterly, the setTimeout() call( also used inside detention( 0)) is touched off, and only also the data source loads its data
The lading flag is set to true, and the lading index will now be displayed
Angular homestretches render the view and reflect the rearmost changes on the screen, which causes the lading index to get displayed. This eliminates the error, which fixes the error communication.
Moving the initialization of the data to ngOnInit()
But in this case, an indeed better result exists! The core of the problem is that we're modifying the data being displayed( the lading flag) inside ngAfterViewInit().
So let's remove the call to startWith( null) that loads the original runner, and rather, lets detector the lading of the original data in ngOnInit()
@Component({
selector: 'course',
templateUrl: './course.component.html'
})
export class CourseComponent implements AfterViewInit, OnInit {
@ViewChild(MatPaginator) paginator: MatPaginator;
ngOnInit() {
// load the initial page
this.dataSource.loadLessons(...);
}
ngAfterViewInit() {
this.paginator.page
.pipe(
tap(() => this.dataSource.loadLessons(...))
).subscribe();
}
}
This also solves the issue, with this we do not have the error presently.
With this new interpretation, there's no revision of the template data in the ngAfterViewInit() lifecycle hook, so the problem doesn't do.
Let's now wrap effects up by talking about what would be if this error would NOT be thrown.
Conclusions
In summary, Angular protects us from erecting programs that are hard to maintain in the long- term and reason about, by throwing the error" Expression has changed after it was checked"( only in development mode).
Although a bit surprising at first sight, this error is constructive!
Why" Expression has changed after it was checked" is useful
What would be if the view generation process could modify the rendered data? This could be veritably problematic, to start we could indeed produce a horizonless circle!
Further generally, then what would imagine having a UI that behaves in an erratic way, where occasionally the stoner can not see the data in our element, and aimlessly sees some former interpretation of the data.
Also, the stoner clicks or hovers some unconnected UI rudiments which are to spark an event tutor, and now another unconnected factor is affected. This kind of error can be veritably hard to reproduce, troubleshoot, and reason about.
One of the main pretensions of using a web frame like Angular is the guarantee that the data in our factors will always get reflected rightly in the view, and that we do not have to do that synchronization ourselves.