Asked  7 Months ago    Answers:  5   Viewed   26 times

Why is the component in this simple plunk

@Component({
  selector: 'my-app',
  template: `<div>I'm {{message}} </div>`,
})
export class App {
  message:string = 'loading :(';

  ngAfterViewInit() {
    this.updateMessage();
  }

  updateMessage(){
    this.message = 'all done loading :)'
  }
}

throwing:

EXCEPTION: Expression 'I'm {{message}} in App@0:5' has changed after it was checked. Previous value: 'I'm loading :( '. Current value: 'I'm all done loading :) ' in [I'm {{message}} in App@0:5]

when all I'm doing is updating a simple binding when my view is initiated?

 Answers

83

First, note that this exception will only be thrown when you're running your app in dev mode (which is the case by default as of beta-0): If you call enableProdMode() when bootstrapping the app, it won't get thrown (see updated plunk).

Second, don't do that because this exception is being thrown for good reason: In short, when in dev mode, every round of change detection is followed immediately by a second round that verifies no bindings have changed since the end of the first, as this would indicate that changes are being caused by change detection itself.

In your plunk, the binding {{message}} is changed by your call to setMessage(), which happens in the ngAfterViewInit hook, which occurs as a part of the initial change detection turn. That in itself isn't problematic though - the problem is that setMessage() changes the binding but does not trigger a new round of change detection, meaning that this change won't be detected until some future round of change detection is triggered somewhere else.

The takeaway: Anything that changes a binding needs to trigger a round of change detection when it does.

Update in response to all the requests for an example of how to do that: @Tycho's solution works, as do the three methods in the answer @MarkRajcok pointed out. But frankly, they all feel ugly and wrong to me, like the sort of hacks we got used to leaning on in ng1.

To be sure, there are occasional circumstances where these hacks are appropriate, but if you're using them on anything more than a very occasional basis, it's a sign that you're fighting the framework rather than fully embracing its reactive nature.

IMHO, a more idiomatic, "Angular2 way" of approaching this is something along the lines of: (plunk)

@Component({
  selector: 'my-app',
  template: `<div>I'm {{message | async}} </div>`
})
export class App {
  message:Subject<string> = new BehaviorSubject('loading :(');

  ngAfterViewInit() {
    this.message.next('all done loading :)')
  }
}
Tuesday, June 1, 2021
 
Tucker
answered 7 Months ago
13

Run change detection explicitly after the change:

import { ChangeDetectorRef } from '@angular/core';

constructor(private cdRef:ChangeDetectorRef) {}

ngAfterViewChecked()
{
  console.log( "! changement de la date du composant !" );
  this.dateNow = new Date();
  this.cdRef.detectChanges();
}
Wednesday, June 2, 2021
 
Alix
answered 6 Months ago
67

You can inject private cdRef:ChangeDetectorRef and call this.cdRef.detectChanges() at the end of ngOnChanges(). This way Angular runs change detection again and won't complain about previously modified model values.

class MyComponent {
  constructor(private cdRef:ChangeDetectorRef) {}

  ngOnChanges(changes) {
    this.xxx = ...
    ...
    this.cdRef.detectChanges();
  }
}
Saturday, August 21, 2021
 
hpk42
answered 4 Months ago
89

You can notice that such parameters can't be used for root component. See this question for more details:

  • Angular 2 input parameters on root directive

The workaround consists in leveraging the ElementRef class. It needs to be injected into your main component:

constructor(elm: ElementRef) {
  this.someattribute = elm.nativeElement.getAttribute('someattribute'); 
}

We need to use the component this way in the HTML file:

<my-app someattribute="somevalue"></my-app>
Saturday, September 18, 2021
 
Skipper
answered 3 Months ago
16

Try to import MdRippleModule in your AppModule:

import { NgModule }      from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

import { AppComponent }  from './app.component';
import { HttpModule} from '@angular/http';
import { MdRippleModule } from '@angular2-material/core/core'; <== this line

@NgModule({
  imports:      [ BrowserModule, HttpModule, MdRippleModule ], <== add here
  declarations: [ AppComponent ],
  bootstrap:    [ AppComponent ]
})
export class AppModule { }

Or pass MdRipple directive within your component:

...
import { MdRipple } from '@angular2-material/core/core'; <== this line
@Component({
  moduleId: module.id,
  selector: 'nav-bar',
  templateUrl: 'nav-bar.component.html',
  styleUrls: ['nav-bar.component.css'],
  directives: [ 
    MdToolbar, 
    MdButton,
    MdIcon,
    MdRipple <== add here
  ],
  providers: [ 
    MdIconRegistry 
  ]
})
export class NavBarComponent implements OnInit {
...
Tuesday, November 9, 2021
 
napolux
answered 3 Weeks ago
Only authorized users can answer the question. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :  
Share