3
votes

Am currently Developing a website using Angular 2/4. Could anyone please let me know how to add a class dynamically to a Div element using Angular. Apparently, am trying to add random bootstrap css color classes to each div background displayed via *ngFor

//Component class
bgarray = ["bg-primary","bg-success","bg-danger","bg-warning","bg-warninig"];

getColour()
{
this.bgColour = this.bgarray[Math.floor(Math.random()*this.bgarray.length)];
return this.bgColour;
}

I have tried using [ngClass] to call a function and the function returns a single class as "bg-danger" or "bg-warning" etc.

<div class="bg-gray-lighter progress progress-xs">
<div class="progress-bar progress-xs " [style.width]="device.value" [ngClass]="getColour()" ></div>
 </div>

But then it throws an error "expressionChangedAfterItHasBeenCheckedError"

Could you please tell me if there's any workaround to make add a different background color to different progress bars dynamically?

Thanks a ton. Much appreciable if you can help

Here's a sneak peek of what I want to achieve.

view them here

PS: The Progress bar count may Vary, below image contains 4 but it can reach 16 or more in future, hence I would like to dynamically assign a random color to them.

4
Don't bind to methods, instead assign the value to a field and bind to that field instead.Günter Zöchbauer

4 Answers

2
votes

The root cause of the problem is that you are calculating "random" the class, and this is not compatible with Change Detection of Angular.

Angular, by design, wants to enforce unidirectional data flow. This means that template binding expressions can not change the model. To check this, Angular at development time controls that the view does not change after it has been rendered, and this is done running the rendering logic a second time and checking that this is exactly equal to what was produced by the rendering engine the first time.

Now, if you have a binding expression which contains some "random generation logic" it may well be that the result produced the by the second rendering is not the same as the result produced during the first run of the rendering engine. This is the reason why you get the expressionChangedAfterItHasBeenCheckedError error.

These links have a good explanation of the unidirectional data flow mechanism in Angular: http://blog.angular-university.io/angular-2-what-is-unidirectional-data-flow-development-mode/ http://blog.angular-university.io/angular-2-redux-ngrx-rxjs/

If you want to assign random classes to your divs, than in the ngOnInit() metho you may create an array and push as many random classes as your divs. You can than use this array to set the classes.

1
votes

try this.

In HTML

<div class="bg-gray-lighter progress progress-xs">
<div class="progress-bar progress-xs " [style.width]="device.value" [ngStyle]="getColour()" ></div>
</div>

in TypeScript file.

public getColour(): any {
        let style: any = null;
        style = {
            'background-color': this.bgarray[Math.floor(Math.random()*this.bgarray.length)]
        }
        return style;
    }
0
votes

Don't forget about Direct DOM manipulation.
Most of the time, Angular does this for you, but you should be aware.

Here is a interactive example: https://plnkr.co/edit/igHxH7I2EJ7HTAN3ymOY?p=preview

Preview of the code:

  // My stuff
  activeBg;
  bgarray     = ["bg-primary", "bg-success", "bg-danger", "bg-warning", "bg-error"];

  getRandomBg() {
    return this.bgarray[Math.floor(Math.random()*this.bgarray.length)];
  }


  updateProgressBtnBg(){
    //debugger;

    // Regular DOM
    let myProgressBar = document.querySelector(".progress-bar");

    if( !this.activeBg ){
      let newRandomBg = this.getRandomBg();
      myProgressBar.classList.add(newRandomBg);
      this.activeBg   = newRandomBg;
    } else {
      let newRandomBg = this.getRandomBg();
      myProgressBar.classList.remove(this.activeBg);
      this.activeBg   = newRandomBg;
      myProgressBar.classList.add(this.activeBg); 
    }

  }


  onMyButtonClick(){
    this.updateProgressBtnBg();
    alert("Random background performed from: onMyButtonClick() ");
  }


  ngOnInit() {
    this.updateProgressBtnBg();
    alert("Random background performed from: ngOnInit() ");
  }
0
votes

I'm not sure this will work. But you can give it a try.

<div class="bg-gray-lighter progress progress-xs">
<div class="progress-bar progress-xs " [style.width]="device.value 
 [ngClass]="{getColour():true}" ></div>
</div>


bgarray = ["bg-primary","bg-success","bg-danger","bg-warning","bg-warninig"];

getColour()
{
this.bgColour = this.bgarray[Math.floor(Math.random()*this.bgarray.length)];
return this.bgColour;
}