0

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);
}  
}
ArunM
  • 2,274
  • 3
  • 25
  • 47
  • Possible duplicate https://stackoverflow.com/questions/43375532/expressionchangedafterithasbeencheckederror-explained. Helpful link https://blog.angularindepth.com/everything-you-need-to-know-about-the-expressionchangedafterithasbeencheckederror-error-e3fd9ce7dbb4 – Sachin Gupta Mar 22 '19 at 06:27
  • If you are not dependent on the `buttontext` property. Then a cleaner approach would be to use `

    `

    – ashish.gd Mar 22 '19 at 06:37

3 Answers3

4

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);
  }
Dmitriy Snitko
  • 931
  • 5
  • 14
3

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.

1

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.


app.component.scss | css

.d-none {
  display: none
}

app.component.html

<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>

app.component.ts | Anywhere

ngOnInit() {
  this.mainSectionContentService.announceContent({
    mainheading:'Employee Manger',
    mainsubheading:'To manage PrivilegeManager Employees',
    sectionheading:'Employee List',
    buttontext:'Create Employee'
  });
}
Vixson
  • 589
  • 7
  • 8