12

I need to change the color of angular material progress bar dynamically. I realized that it is not that simple, so what is the best way to to it?

requirements:

  1. I will receive the color hex code from an external API. So I can not create a set of predefined themes
  2. The background color will be white. So, I just need one color, not the entire palette (lighter, darker) colors.

related link: (1)

guilhermecgs
  • 2,913
  • 11
  • 39
  • 69
  • Probably the duplicate of [Angular - Material: Progressbar custom color?](https://stackoverflow.com/questions/48869915/angular-material-progressbar-custom-color) – Exterminator Sep 14 '18 at 11:19
  • I honestly don't think this possible to do without a hack of some sort. Theming will be improved in the future as stated in the official doc. – Ploppy Sep 14 '18 at 11:20
  • 1
    Watch the answer of Simonca in the linked question. Join that solution with `ngClass` – Christian Vincenzo Traina Sep 14 '18 at 11:21
  • Possible duplicate of [Angular - Material: Progressbar custom color?](https://stackoverflow.com/questions/48869915/angular-material-progressbar-custom-color) – Edric Sep 15 '18 at 04:10
  • Not duplicate. How to receive the color and apply it dynamically? – guilhermecgs Sep 15 '18 at 10:27
  • I'm having the same issue. I'm getting all colors, logos, content, etc. out of a CMS and have to programmatically change the page accordingly. Has anyone found a clean solution to this? The selected answer doesn't seem to fulfill the requirements. – Ben Roberts Jul 03 '19 at 20:51
  • @BenRoberts WHat about Ryan Meyers' answer? – David Jul 04 '19 at 07:40
  • @guilhermecgs did you get the solution change it dynamically ? – Joel Joseph Jul 10 '19 at 10:15
  • @JoelJoseph, I just need to change between 4 predefined colors. So I hardcoded each color on the css. I used @meetdave´s solution – guilhermecgs Jul 10 '19 at 13:07
  • this should be on the component api! plz angular team! – guilhermecgs Jul 10 '19 at 13:10

5 Answers5

20

We can create an attribute directive which accepts the color value and override default styles of <mat-progress-bar> for us.

here is a working demo : https://stackblitz.com/edit/material-progress-bar-color-directive

here is a brief explanation:

If we inspect <mat-progress-bar> in developer tools. we will find that color of the progress-bar is defined in the ::after pseudo element like this.

.mat-progress-bar-fill::after {
    background-color: #3f51b5;
}

And as we already know that it is not possible to directly manipulate a pseudo element using DOM querySelector() method. But we can add new styles which can have rules for pseudo elements too. checkout this thread for more details. https://stackoverflow.com/a/21709814/1160236

So, we can make a directive which can take care of adding new styles for us.

import { Directive, Input, OnChanges, SimpleChanges, ElementRef } from '@angular/core';

@Directive({
  selector: '[appProgressBarColor]'
})
export class ProgressBarColor implements OnChanges{
  static counter = 0;

  @Input() appProgressBarColor;
  styleEl:HTMLStyleElement = document.createElement('style');

  //generate unique attribule which we will use to minimise the scope of our dynamic style 
  uniqueAttr = `app-progress-bar-color-${ProgressBarColor.counter++}`;

  constructor(private el: ElementRef) { 
    const nativeEl: HTMLElement = this.el.nativeElement;
    nativeEl.setAttribute(this.uniqueAttr,'');
    nativeEl.appendChild(this.styleEl);
  }

  ngOnChanges(changes: SimpleChanges): void{
    this.updateColor();
  }

  updateColor(): void{
    // update dynamic style with the uniqueAttr
    this.styleEl.innerText = `
      [${this.uniqueAttr}] .mat-progress-bar-fill::after {
        background-color: ${this.appProgressBarColor};
      }
    `;
  }

}

as you can see that all that we are doing here is just making a new HtmlStyleElement and adding it just inside the host element.

And inside updateColor() method we are updating the innerText of the style tag we have appended. notice that we are using an attribute selector here with a unique attribute to minimize the scope of the style to the host only. because we want to override the style only for that progress-bar on which we have applied our directive.

you can use this directive in your template like this.

<mat-progress-bar [appProgressBarColor]="'orange'"
                  mode="determinate" 
                  value="40"></mat-progress-bar>

I hope this will help.

HirenParekh
  • 3,655
  • 3
  • 24
  • 36
  • 1
    I couldn't get this to work in Angular 11 until I added a class name to the native element and the style. For example, "nativeEl.classList.add('progress-bar-custom-color');" and ".progress-bar-custom-color[${this.uniqueAttr}] .mat-progress-bar-fill::after"... – Ryan Haney Jun 03 '21 at 17:28
  • WOW !!!!... Awesome solution !!! – RDV Apr 23 '22 at 00:42
13

Update:

Avoid using deep, TL;DR: Deep is technically invalid (like, deeprecated :p)

For more info refer: The use of /deep/ and >>> in Angular 2

Now, to change the color of mat-progress bar,

Head over to your styles.scss file (or the main css/scss file in your project)

Add this class -->

.green-progress .mat-progress-bar-fill::after {
    background-color: green !important;
}

Your mat-progress should use the above class, like -->

<mat-progress-bar class="green-progress" mode="indeterminate"></mat-progress-bar>
Meet Dave
  • 990
  • 10
  • 11
3

I ran into this issue and none of the solutions proposed actually solved the problem as I understood it. After working though some different ways of thinking about it, I believe that I figured out a manageable solution for dynamically changing the colors of mat-progress-bar when they are unknown before an API call (or user input). I've created an example on stackblitz to play with, but it ultimately works like this:

  1. On init, create a style HTMLElement that you can reference later.
  2. On whatever trigger you desire (in the example, it's change of the input value) clear the style element, re-assign the colors to custom css, and re-attach to head
// in app.component.ts

  styleElement: HTMLStyleElement;
  colors : Array<string> = ["#ff00ff", "#00ff00"];

changeColors() {
  const head = document.getElementsByTagName('head')[0];
  const css = `
  .style1 .mat-progress-bar-fill::after {
    background-color: ${this.colors[0]} !important;
  }

  .style2 .mat-progress-bar-fill::after {
    background-color: ${this.colors[1]} !important;
  }
  `;
  this.styleElement.innerHTML = '';
  this.styleElement.type = 'text/css';
  this.styleElement.appendChild(document.createTextNode(css));
  head.appendChild(this.styleElement);

}

ngOnInit() {
  this.styleElement = document.createElement('style');
  this.changeColors();
}
<!-- In app.component.html -->
<p>
  <mat-progress-bar mode="determinate" value=70 class="style1"></mat-progress-bar>
</p><p>
  <mat-progress-bar mode="determinate" value=40 class="style2"></mat-progress-bar>
</p>
<div><mat-form-field><input matInput type="color" placeholder="Style 1" [(ngModel)]="colors[0]" (change)="changeColors()" /></mat-form-field>
<mat-form-field><input matInput type="color" placeholder="Style 2" [(ngModel)]="colors[1]" (change)="changeColors()" /></mat-form-field></div>
2

mat-progress-bar

/deep/ .mat-progress-bar-fill::after {
    background-color: green;
}

/deep/ .mat-progress-bar-buffer {
    background: #E4E8EB;
}
SaiLekkala
  • 41
  • 5
1

Angular Material v15 components are now based on the official Material Design components for Web (MDC) which leads to some internal classes being changed. This leads to the answer from @HirenParekh no longer working. I updated the changeColor() method from his answer and extended it to also change the color of the buffer (the part of the progress bar not filled):

  updateColor(): void{
    // update dynamic style with the uniqueAttr
    this.styleEl.innerText = `
      [${this.uniqueAttr}] .mdc-linear-progress__bar-inner {
        border-color: ${this.progressBarColor};
      }

      [${this.uniqueAttr}] .mdc-linear-progress__buffer-bar {
        background-color: ${this.progressBarColor}55 !important;
      }
    `;
  }
grimsi
  • 11
  • 4