2

In the Angular2 documentation on template syntax the sample code for the preferred method for setting multiple styles at the same time is this:

setStyles() {
  let styles = {
    // CSS property names
    'font-style':  this.canSave      ? 'italic' : 'normal',  // italic
    'font-weight': !this.isUnchanged ? 'bold'  : 'normal',  // normal
    'font-size':  this.isSpecial    ? 'x-large': 'smaller', // larger
  }
  return styles;
}

Called like this:

<div [ngStyle]="setStyles()">
  This div is italic, normal weight, and x-large
</div>

However, trying out this code in the Tour of Heroes project causes the following error:

Angular 2 is running in the development mode. Call enableProdMode() to enable the production mode.
EXCEPTION: Error during evaluation of "click"
EXCEPTION: Expression 'setClasses() in AppComponent@11:10' has changed after it was checked.
Previous value: '[object Object]'. Current value: '[object Object]' in [setClasses() in AppComponent@11:10]

I understand this is part of Angular2’s drive to emphasise unidirectional data flow, but given this example as a best practice, what’s going on?

I found one suggested solution to this issue here, which has no accepted answer. The discussion takeaway: Anything that changes a binding needs to trigger a round of change detection when it does. In the notes there was a link to another answer which said this: manually run change detection:

Use ApplicationRef::tick() method.
Use NgZone::run() method to wrap you code which should be executed inside angular zone.

Trying to then use the method app.tick(); and NgZone::run() both break the entire application, or I’m just using it wrong.

I have created this Plunker to demonstrate this issue. When you select a hero, the styles on their name should change depending on the data they have available in their model. For example, the first five heroes have a standard style, but the next three, Dr IQ, SkyDog and J-Dragon have alter egos and powers. In development mode, you can only select one and the app breaks with the message: Expression 'setClasses() in AppComponent@11:10' has changed after it was checked.

If you un-comment out the //enableProdMode(); in boot.ts, then the app works fine.

So what’s up with that? I want to be able to use development mode and use ngClass in this way. What is the best practice for doing this?

Community
  • 1
  • 1
curchod
  • 271
  • 1
  • 3
  • 16
  • You probably can't call `tick()` or `run()` while change detection is evaluating `setStyles()`. The problem is that `setStyles()` returns a new object each time it is called. Try making the object a component property instead, and just change the object property values in `setStyles()`. – Mark Rajcok Jan 16 '16 at 23:07

1 Answers1

1

Change:

<h4 [ngClass]="setClasses()">

for:

<h4 [ngClass]="classes">

and call the setClasses method in the onSelect method:

onSelect(hero: Hero) { 
    this.selectedHero = hero; 
    hero.power ? this.stylar1 = true: this.stylar1 = false;
    hero.alterEgo ? this.stylar2 = true: this.stylar2 = false;
    this.setClasses();
}

http://plnkr.co/edit/95H4YNtdfn3y2KTdX8Xu?p=preview

Langley
  • 5,326
  • 2
  • 26
  • 42