Cory Rylan

My name is , Google Developer Expert, Speaker, Software Developer. Building Design Systems and Web Components.

Follow @coryrylan
Angular

Angular Progress Component with SVG

Cory Rylan

- 4 minutes

Updated

This article has been updated to the latest version Angular 17 and tested with Angular 16. The content is likely still applicable for all Angular 2 + versions.

Often when building an application with visualizations, we reach for a third party library. Many of these libraries are great for display charts and graphs with a rich feature set. Sometimes though we need something lightweight and don't want to pull in another dependency to our Angular app. In this post, we are going to look at creating a progress visualization component and see how easy it is to do this with Angular and SVG.

Our component will look like the example below.

SVG Template and CSS Styles

To create this component, we are going to use SVG to render the circle and then Angular to update the SVG efficiently. First, let's take a look at the SVG.

<svg width:"120" height:"120" viewBox="0 0 120 120" class="progress__svg">
  <circle
    [attr.r]="radius"
    cx="60"
    cy="60"
    stroke-width="12"
    class="progress__meter"
  />
  <circle
    [style.strokeDasharray]="circumference"
    [style.strokeDashoffset]="dashoffset"
    [attr.r]="radius"
    cx="60"
    cy="60"
    stroke-width="12"
    class="progress__value"
  />
</svg>

Out SVG consists of two circles. One circle will be the outer grey while the other will be the progress value that will be updated by Angular. If we look at our SVG, the first thing we do is bind the circumference value to each circle.
The circumference value will be set in the component class. Notice that we can use Angular's property binding syntax [] to set properties and attributes of our SVG.

<circle
  [style.strokeDasharray]="circumference"
  [style.strokeDashoffset]="dashoffset"
  [attr.r]="radius"
  cx="60"
  cy="60"
  stroke-width="12"
  class="progress__value"
/>

Looking at the second circle, we bind two style properties, strokeDasharray and strokeDashoffset. Using these style properties, we can calculate the progress value in our component and Angular will automatically update the styles to reflect the changes.

Now we need to write a bit of CSS to style everything correctly on our component. This CSS sets a few style properties on our SVG such as color and fill.

.progress__svg {
  transform: rotate(-90deg);
}

.progress__meter,
.progress__value {
  fill: none;
}

.progress__meter {
  stroke: #ccc;
}

.progress__value {
  stroke: #4caf50;
  transition: all;
}

Component TypeScript

Now that we have our template and CSS set up we need to write the logic to update our progress meter.

import {
  Component,
  Input,
  OnInit,
  OnChanges,
  SimpleChanges
} from '@angular/core';

@Component({
  selector: 'app-progress',
  templateUrl: './progress.component.html',
  styleUrls: ['./progress.component.css']
})
export class ProgressComponent implements OnInit, OnChanges {
  radius = 54;
  circumference = 2 * Math.PI * this.radius;
  dashoffset: number;

  constructor() {
    this.progress(0);
  }

  ngOnInit() {}

  private progress(value: number) {
    const progress = value / 100;
    this.dashoffset = this.circumference * (1 - progress);
  }
}

We will first start will our calculations to render the SVG correctly. In our component, we define the following properties, radius, circumference and dashoffset. Radius is a constant value that our SVG circles bind to for their radius attribute. We then calculate the circumference to bind to our second circle's style property strokeDasharray, this creates our circle. We then calculate the strokeDashoffset which is what creates our green fill effect.

To calculate the strokeDashoffset, we create a method called progress(). The progress() method takes in a value 0 - 100 and then computes the
appropriate offset value to set to the dashoffset property. In our constructor, we initialize this value to 0. If we looked at our component, currently we wouldn't see any progress value. We ideally want to be able to pass the progress value into this component so that other components can use it efficiently.

Inputs and ngOnChanges

To make this component reusable, we are going to add a @Input property called value. With this, we can pass in a given value from a parent component and render that progress value.

import {
  Component,
  Input,
  OnInit,
  OnChanges,
  SimpleChanges
} from '@angular/core';

@Component({
  selector: 'app-progress',
  templateUrl: './progress.component.html',
  styleUrls: ['./progress.component.css']
})
export class ProgressComponent implements OnInit, OnChanges {
  @Input() value: number;
  radius = 54;
  circumference = 2 * Math.PI * this.radius;
  dashoffset: number;

  constructor() {
    this.progress(0);
  }

  ngOnInit() {}

  ngOnChanges(changes: SimpleChanges) {
    if (changes.value.currentValue !== changes.value.previousValue) {
      this.progress(changes.value.currentValue);
    }
  }

  private progress(value: number) {
    const progress = value / 100;
    this.dashoffset = this.circumference * (1 - progress);
  }
}

Adding the @Input() value property is not enough. We now need to implement the ngOnChanges life cycle hook. The ngOnChanges will notify us whenever an input property changes, allowing us to efficiently trigger when the progress() method should be called to recalculate our dashoffset.

Once set up we can now use our component in our template like so:

<app-progress [value]="75"></app-progress>
Angular Progress Component

As we can see Angular's binding syntax allows us to quickly update SVG elements and create fast and efficient svg animations for our apps. Check out the full working demo below!

View Demo Code   
Twitter Facebook LinkedIn Email
 

No spam. Short occasional updates on Web Development articles, videos, and new courses in your inbox.

Related Posts

Angular

Creating Dynamic Tables in Angular

Learn how to easily create HTML tables in Angular from dynamic data sources.

Read Article
Web Components

Reusable Component Patterns - Default Slots

Learn about how to use default slots in Web Components for a more flexible API design.

Read Article
Web Components

Reusable Component Anti-Patterns - Semantic Obfuscation

Learn about UI Component API design and one of the common anti-patterns, Semantic Obfuscation.

Read Article