Angular – Changing CSS with ElementRef

Overview

This is a tutorial for building a horizontal meter with 3 fill color values.

Figure 1

Each of the values on the inside can be modified dynamically with typescript using ViewChild() and ElementRef. With ElementRef you can modify properties of a CSS selector. In this example we will modify the width but you can also modify properties such as height, color, margin, padding, transform etc.

Create Component

First generate a new component called average-meter.

ng generate component average-meter

You should see the following files:

Build the meter with HTML and CSS.

HTML average-meter.component.html

<div class="bar">
  <div class="low-end">
    <p>20%</p>
  </div>
  <div class="avg-mid">
    <p>20%</p>
  </div>
  <div class="high-end">
      <p>20%</p>
  </div>
</div>

CSS

average-meter.component.scss or average-meter.component.css

.bar {
  width: 100%;
  height: 30px;
  border-radius: 10px;
  background-color: #b3b3b3;
  color: white;
  text-align: center;
}

.low-end {
  width: 33%;
  height: 100%;
  background-color: green;
  float: left;
  border-top-left-radius: 10px;
  border-bottom-left-radius: 10px;
}

.avg-mid {
  width: 33%;
  height: 100%;
  background-color: orange;
  float: left;
}

.high-end {
  width: 33%;
  height: 100%;
  background-color: red;
  float: left;
}

You should see the bar in Figure 1.

This image has an empty alt attribute; its file name is image-2.png

Next set up the TypeScript file: average-meter.component.ts

Import the following:

import { Component, OnInit, ViewChild, ElementRef, AfterViewInit, Input } from '@angular/core';

Add the Inputs since we want to make this component reusable. Also add the Element References.

  @Input() lowEndVal: number;
  @Input() midAvgVal: number;
  @Input() highEndVal: number;
  @ViewChild('lowEnd') lowEnd: ElementRef;
  @ViewChild('avgMid') avgMid: ElementRef;
  @ViewChild('highEnd') highEnd: ElementRef;

We will have to use AfterViewInit since we want to make the change to an HTML element that has already been rendered.

export class AverateMeterComponent implements OnInit, AfterViewInit {

The ngAfterInit() the logic will look like the following:

  ngAfterViewInit() {
    this.lowEndVal = 20;
    this.midAvgVal = 20;
    this.highEndVal = 20;
    this.lowEnd.nativeElement.style.width = this.lowEndVal + '%';
    this.avgMid.nativeElement.style.width = this.midAvgVal + '%';
    this.highEnd.nativeElement.style.width = this.highEndVal + '%';
  }

All together for the component TypeScript file:

import { Component, OnInit, ViewChild, ElementRef, AfterViewInit, Input } from '@angular/core';

@Component({
  selector: 'app-averate-meter',
  templateUrl: './total-averate-meter.component.html',
  styleUrls: ['./total-averate-meter.component.scss']
})
export class AverateMeterComponent implements OnInit, 

AfterViewInit {
  @Input() lowEndVal: number;
  @Input() midAvgVal: number;
  @Input() highEndVal: number;
  @ViewChild('lowEnd') lowEnd: ElementRef;
  @ViewChild('avgMid') avgMid: ElementRef;
  @ViewChild('highEnd') highEnd: ElementRef;

  ngAfterViewInit() {
    this.lowEnd.nativeElement.style.width = this.lowEndVal + '%';
    this.avgMid.nativeElement.style.width = this.midAvgVal + '%';
    this.highEnd.nativeElement.style.width = this.highEndVal + '%';
  }

  ngOnInit(): void {
   // Hard coded values to test it
    this.lowEndVal = 20;
 
    this.midAvgVal = 20;
    this.highEndVal = 20;
  }

}

In the HTML template add the ViewChild Element Reference attributes.


<div class="low-end" #lowEnd>
<div class="avg-mid" #avgMid>
<div class="high-end" #highEnd>
<div class="bar">
  <div class="low-end" #lowEnd>
    <p>{{lowEndVal}}%</p>
  </div>
  <div class="avg-mid" #avgMid>
    <p>{{midAvgVal}}%</p>
  </div>
  <div class="high-end" #highEnd>
      <p>{{highEndVal}}%</p>
  </div>
</div>

When you are ready to use this from another component just remove the hard coded values and pass them in as follows:

<app-average-meter [lowEndVal]="YOURLOWVALVAR"
                     [midAvgVal]="YOURMIDVALVAR"
                     [highEndVal]="YOURHIGHVALVAR" />

This next part is completely Optional:

To make your components a little nicer we can remove the app from app-average-meter in the average-meter.component.ts file by updating the selector under @Component.

Change selector: ‘app-average-meter’ to selector: ‘average-meter’,

@Component({
  selector: 'average-meter',
  templateUrl: './average-meter.component.html',
  styleUrls: ['./average-meter.component.scss']
})

Now you can call it as

<average-meter [lowEndVal]="YOURLOWVALVAR"
               [midAvgVal]="YOURMIDVALVAR"
               [highEndVal]="YOURHIGHVALVAR" />

In addition to changing CSS elements, @ViewChild can change the entire class with nativeElement.className.

For example: A CSS file with two class definitions

.blue-font{
 background-color: 'white';
 color: 'darkblue';
}
.white-font{
 background-color: 'darkblue';
 color: 'white';
}

From the HTML file:

<span class="blue-font" #HeaderSpan>Hello World</span>
<button type="button" (click)="onChangeFont('blue')">Blue</button>
<button type="button" (click)="onChangeFont('blue')">Blue</button>

From the component typescript file:

import { Component, ViewChild, ElementRef } from '@angular/core';
....
....
@ViewChild('HeaderSpan') formCardElement: ElementRef;
....
....
 public onChangeFont(color: string) {

    if(color === 'blue') {
       this.SpanElement.nativeElement.className  = 'blue-font'; 
    } else {
       this.SpanElement.nativeElement.className  = 'white-font'; 
    }
 }