Reputation: 2314
Why do I get the following warning in console ? Everything seems to be working as expected but Angular complains. What would be a solution for this issue ?
StackBlitz is here
I know a possible solution is to communicate event via parent child communication instead of using service but that is not an option for me since this is an isolation of a problem in a larger code base.
Error Message
ERROR Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'ngIf: false'. Current value: 'ngIf: true'.
at viewDebugError (core.js:20439)
at expressionChangedAfterItHasBeenCheckedError (core.js:20427)
at checkBindingNoChanges (core.js:20529)
at checkNoChangesNodeInline (core.js:23400)
at checkNoChangesNode (core.js:23389)
at debugCheckNoChangesNode (core.js:23993)
at debugCheckDirectivesFn (core.js:23921)
at Object.eval [as updateDirectives] (AppComponent.html:6)
at Object.debugUpdateDirectives [as updateDirectives] (core.js:23910)
at checkNoChangesView (core.js:23288)
app.component.html
<div style="text-align:center">
<h1>
Welcome to {{ title }}!
</h1>
<h1 *ngIf="mainSectionContent?.buttontext?.length > 0">
Welcome to {{ title }}!
</h1>
<app-employee></app-employee>
</div>
AppComponent
export class AppComponent implements OnInit {
title = 'expression-changed';
mainSectionContent:MainSectionContent;
contentAnnounce$:Observable<MainSectionContent>;
constructor(private mainContentService:MaincontentService) { }
ngOnInit(): void {
this.contentAnnounce$ = this.mainContentService.contentAnnounce$;
this.contentAnnounce$.subscribe(mainSectionContent =>
{
this.mainSectionContent = mainSectionContent
}
);
}
}
EmployeeComponent
export class EmployeeComponent implements OnInit {
constructor(private mainSectionContentService:MaincontentService) { }
ngOnInit() {
this.mainSectionContentService.announceContent({
mainheading:'Employee Manger',
mainsubheading:'To manage PrivilegeManager Employees',
sectionheading:'Employee List',
buttontext:'Create Employee'
});
}
}
MaincontentService
@Injectable({
providedIn: 'root'
})
export class MaincontentService {
private contentAnnounce = new Subject<MainSectionContent>();
contentAnnounce$ = this.contentAnnounce.asObservable();
constructor() { }
announceContent(content:MainSectionContent){
this.contentAnnounce.next(content);
}
}
Upvotes: 1
Views: 1610
Reputation: 1985
You could move the critical part to a new child component like
app-component.html
<div style="text-align:center">
<h1>
Welcome to {{ title }}!
</h1>
<app-welcome [title]="title"></app-welcome>
<app-employee></app-employee>
</div>
app-component.ts
export class AppComponent implements OnInit {
title = 'expression-changed';
}
welcome-component.html
<h1 *ngIf="mainSectionContent?.buttontext?.length > 0">
Welcome to {{ title }}!
</h1>
welcome-component.ts
export class WelcomeComponent implements OnInit {
@Input() title!: string;
mainSectionContent:MainSectionContent;
contentAnnounce$:Observable<MainSectionContent>;
constructor(private mainContentService:MaincontentService) { }
ngOnInit(): void {
this.contentAnnounce$ = this.mainContentService.contentAnnounce$;
this.contentAnnounce$.subscribe(mainSectionContent =>
{
this.mainSectionContent = mainSectionContent
}
);
}
}
Then the error should disappear.
Upvotes: 0
Reputation: 739
Ok this ExpressionChangedAfterItHasBeenCheckedError: error has been going on for a while now and it seems the only and best hack-around is to use the:
setTimeout(() => {...changes...});
---or---
Promise.resolve(null).then(() => {...changes...});
But to archive that you have to implement this on every component or area, you want to make those changes. That could be a real hassle.
So I think the best is to bind class display: none
on that HtmlElement you want to hide.
.d-none {
display: none
}
<div style="text-align:center">
<h1>
Welcome to {{ title }}!
</h1>
<h1 [class.d-none]="mainSectionContent?.buttontext?.length > 0">
Welcome to {{ title }}!
</h1>
<app-employee></app-employee>
</div>
ngOnInit() {
this.mainSectionContentService.announceContent({
mainheading:'Employee Manger',
mainsubheading:'To manage PrivilegeManager Employees',
sectionheading:'Employee List',
buttontext:'Create Employee'
});
}
Upvotes: 1
Reputation: 83
I used to face the same problem, but this works in a angular native way, you can use ChangeDetectorRef of angular/core class follow the code below:
constructor(private cdf: ChangeDetectorRef){}
And add this line after you received the data in controller, in your case:
this.mainSectionContent = mainSectionContent
this.cdf.detectChanges();
What this does is asks the angular service to recheck the changes on DOM.
Upvotes: 3
Reputation: 960
The case is that ngOnInit of EmployeeComponent is called after *ngIf="mainSectionContent?.buttontext?.length > 0"
already checked.
First parent component is checked, then goes child EmployeeComponent
, that changes value, already used to render parent component in same event loop iteration.
This looks like a hack, but you have to call announceContent after first loop passes. You can try call it just after the first event loop finishes:
ngOnInit() {
setTimeout(() => {
this.mainSectionContentService.announceContent({
mainheading:'Employee Manger',
mainsubheading:'To manage PrivilegeManager Employees',
sectionheading:'Employee List',
buttontext:'Create Employee'
});
},
0);
}
Upvotes: 5