0

I have the following Angular component (simplified for question):

<md-sidenav-container fxFlex>
  <div fxFlex class="app-content">
    This is the content
  </div>
  <button *ngIf="newsbar.opened" type="button" (click)="closeBar(newsbar)">Close</button>
  <button *ngIf="!newsbar.opened" type="button" (click)="openBar(newsbar)">Open</button>
  <md-sidenav #newsbar mode="side" [opened]="showNewsBar()" align="end" class="app-news-bar">
    <app-news-bar></app-news-bar>
  </md-sidenav>
</md-sidenav-container>

And the following is the function which dictates whether the newsbar is open or not (based on the window width).

showNewsBar() {
  const width = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
  if (width >= 1280) {
    return true;
  } else {
  return false;
  }
}

When running the app, everything works as expected. When the window is smaller or becomes smaller than 1280px, the sidebar closes and the "Open" button is shown. When the window is larger or becomes larger than 1280px, the sidebar opens and the "Close" button is shown. But, whenever the window is resized to trigger a change in the function showNewsBar(), I get an error.

ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'true'. Current value: 'false'.

When I resize the window so it becomes larger and the newsbar opens, Previous value: 'false'. Current value: 'true'.

The line that gives me the error are the buttons. For some reason, Angular doesn't like that I'm binding the *ngIf directives to the local MdSidenav reference's "opened" property.

Again, everything works as expected. Nothing wrong with the functionality. So why does this error occur? And is there a way to fix it?

Jun Kang
  • 1,267
  • 1
  • 10
  • 27
  • Read as much as you can from this [question](https://stackoverflow.com/questions/43375532/expressionchangedafterithasbeencheckederror-explained). To keep it short, `[opened]="showNewsBar()"` is causing this error. Quoting @GünterZöchbauer, "Don't bind to methods or functions in the view, instead bind to fields and update the fields in event handlers. If you must bind to methods, ensure that they always return the same value instance as long as there wasn't actually a change. Change detection will call these methods a lot" – Nehal Aug 09 '17 at 23:15

1 Answers1

3

You will find some good reasons for this error from this question. Basically, [opened]="showNewsBar()" is causing this error.

Quoting @GünterZöchbauer from his answer,

Don't bind to methods or functions in the view, instead bind to fields and update the fields in event handlers. If you must bind to methods, ensure that they always return the same value instance as long as there wasn't actually a change. Change detection will call these methods a lot.

Instead of using [opened]="showNewsBar()", you can bind opened to a boolean variable, and set/change the variable value using the ngOnInit lifecycle hook and window:resize event. Here's an example:

html:

<div fxFlex class="app-content">
  This is the content
</div>
<button *ngIf="openFlag" type="button" (click)="sidenav.close(); openFlag = false">Close</button>
<button *ngIf="!openFlag" type="button" (click)="sidenav.open(); openFlag = true">Open</button>

<md-sidenav-container (window:resize)="onResize($event)" style="height: 91vh;background: #FFFFFF">

  <md-sidenav #sidenav mode="side" [opened]="openFlag" align="end" style="background: orange">
    Sidenav!
  </md-sidenav>

</md-sidenav-container>

Option 1: (window:resize)

ts:

ngOnInit(){
    this.onResize();
  }

  onResize(event) {
    let width;

    if(event != undefined){
      width = event.target.innerWidth;
      console.log(event.target.innerWidth);
    }
    else{
      console.log(document.body.clientWidth);
      width = document.body.clientWidth;
    }

    if (width >= 1280) {
      this.openFlag = true;
    } 
    else {
      this.openFlag = false;
  }

  }

Plunker demo

Option 2: Flex-layout

I see that you have fxFlex means you are using flex-layout. Flex-layout comes with ObservableMedia (doc). You can use that too to set the boolean value of openFlag.

This option doesn't require (window:resize) event in the view.

ts:

import {MediaChange, ObservableMedia} from "@angular/flex-layout"; 

constructor(media: ObservableMedia) {
      media.asObservable()
        .subscribe((change: MediaChange) => {
          if(change.mqAlias == 'lg' || change.mqAlias == 'xl'){
            this.openFlag = true;
          }
          else{
            this.openFlag = false;
          }
        });
  }

Plunker demo

Nehal
  • 13,130
  • 4
  • 43
  • 59
  • As much as I appreciate the effort, and the 2 plunkrs, its not the `[opened]="showNewsBar()"` that is giving the error. It's the `*ngIf="newsbar.opened"` on the buttons that are giving the error. `[opened]="showNewsBar()"` doesn't give me any errors at all. – Jun Kang Aug 10 '17 at 17:09
  • "whenever the window is resized to trigger a change in the function showNewsBar(), I get an error." - quote from the question – Nehal Aug 10 '17 at 17:17
  • Yeah, and that is the truth. I suppose I could have worded it differently. showNewsBar() triggers a change for the `[opened]="showNewsBar()"`. The part that throws the error is the fact that the *ngIf directive on the buttons values are changing. – Jun Kang Aug 10 '17 at 17:26
  • Angular, for some reason, doesn't like that I'm binding the *ngIf directive to the MdSidenav element's "opened" property. – Jun Kang Aug 10 '17 at 17:30
  • Yep, I got realized your problem after your comment, working on improving the answer. – Nehal Aug 10 '17 at 17:31
  • I have updated the answer and both plunkers. As you can see, instead of binding to `newsbar.opened` for `*ngIf`, you can use the same boolean used for `[opened]` which is `openFlag` in my example. When 'Open' and 'Close' buttons are clicked, `openFlag` value can be updated to reflect the current status. Hope this resolves your problem :) – Nehal Aug 10 '17 at 17:48
  • That is definitely a way around the error. Thanks. Do you know why angular doesn't like binding the if directive to that opened property though? I'm faily familiar with the change detection cycles, and I don't see why that would trigger an error on the if directive. – Jun Kang Aug 10 '17 at 17:53
  • From what I understand, Angular is complaining because, or screen size is > 1280, the value of `newsbar.opened` changes from `false` to `true` after the view is initialized and it causes Angular to run few more change detections. – Nehal Aug 10 '17 at 18:00