Step by step progress bar in angular 8

Mon, Mar 16, 2020

Read in 5 minutes

Introduction

In this blog, you will learn to create step by step progress bar like below. For the CSS design , I referred to the codepen https://codepen.io/polinovskyi/pen/embZmw and developed the logic in angular.

progressBar

Design

First, you need to have the design for an arrow style steps. I get the page content from the backend JSON. So the number of steps depends on the array count which is coming from backend.

[
    {
        "steps": [
            {
                "id": "1",
                "key": "PersonalBlock",
                "description": "Personal Information",
                "Elements": [
                    {
                        "id": "2",
                        "key": "name",
                        "description": " Name",
                        "type": "textbox"
                    },
                    {
                        "id": "3",
                        "key": "dob",
                        "description": "Date of Birth",
                        "type": "date"
                    }
                ]
            },
            {
                "id": "2",
                "key": "ContactBlock",
                "description": "Contact Information",
                "Elements": [
                    {
                        "id": "4",
                        "key": "email",
                        "description": "Email Address",
                        "type": "textbox"
                    },
                    {
                        "id": "4",
                        "key": "phonenumber",
                        "description": "PhoneNumber",
                        "type": "textbox"
                    }
                ]
            },
            {
                "id": "3",
                "key": "ProfessionalBlock",
                "description": "Professional Information",
                "Elements": [
                    {
                        "id": "5",
                        "key": "company",
                        "description": " Company",
                        "type": "textbox"
                    },
                    {
                        "id": "6",
                        "key": "position",
                        "description": "Position",
                        "type": "textbox"
                    },
                    {
                        "id": "7",
                        "key": "employmenttype",
                        "description": "Employeement Type",
                        "type": "dropdown",
                        "possibleValues": [
                            "FullTime",
                            "PartTime",
                            "Contract"
                        ]
                    }
                ]
            }
        ]
    }
]

So here in the JSON steps array, three items. So the array count 3 is the number of steps in the progress bar.

Step 1 ,Step 2, Step 3 Logic

<div class="row" *ngFor="let step of stepsArr">
          <div class="eachStep" *ngFor="let s of step.steps;
index as i;">
            <div style="margin:20px">
              <div class="step" [class.current]="i==currentStep"><span> step {{i+1}}
                </span>
              </div>
            </div>
          </div>
        </div>

I am getting this “stepsArr” from the backend JSON. Each step in the progress bar is painted on the above for loop execution.

The logic for getting JSON from the backend

url = 'assets/data.json';
this.http.get<any>(this.url).subscribe(data => {
        console.log(data);
    this.stepsArr = data;})

Populating the content of each step: Each step, the different content should get populated. Each array content is getting populated as content(text boxes/dropdown) for each step on clicking the next button. To achieve this I use child-component. I pass each array block into a child component.

<div *ngFor="let step of stepsArr">
      <div *ngFor="let s of step.steps;index as i">
        <div *ngIf="i==currentStep">
          <app-childcontent [step]="s">
          </app-childcontent>
        </div>
      </div>
      <div class="buttonGroup">
        <button (click)="back(i)" class="btn-primary  alignButton">
        Previous</button>
        <button (click)="next(i)" class="btn-primary alignButton ">
        Next</button>
      </div>
    </div>

Child Component:(childcontent.component.html)

<div *ngFor="let element of step.Elements">
  <div *ngIf="element.type=='textbox'" class="form-group">
      <label>{{element.description}}</label>
      <input type={{element.type}} name={{element.key}} class="form-control" id={{element.key}}
          ngModel>
  </div>
  <div *ngIf="element.type=='date'" class="form-group">
      <label>{{element.description}}</label>
      <input type={{element.type}} name={{element.key}} class="form-control" id={{element.key}}
          ngModel>
  </div>
  <div *ngIf="element.type=='dropdown'" class="form-group">
      <label>{{element.description}}</label>
      <select name={{element.key}} class="form-control" id={{element.key}} ngModel>
          <option *ngFor="let value of element.possibleValues">
              {{value}}
          </option>
      </select>
  </div>
 </div>

ChildcontentComponent.ts

import { Component, OnInit, Input } from '@angular/core';
import { Step } from 'src/model/step';
import { Elements } from 'src/model/elements';

@Component({
 selector: 'app-childcontent',
 templateUrl: './childcontent.component.html',
 styleUrls: ['./childcontent.component.css']
})
export class ChildcontentComponent implements OnInit {

 @Input() step:Step;
 @Input() element:Elements;

 constructor() { }

 ngOnInit() {
 }

}

CSS Design(app.component.css)

/* Global CSS, you probably don't need that */
.clearfix:after {
    clear: both;
    content: "";
    display: block;
    height: 0;
}
.container {
	font-family: 'Lato', sans-serif;
	width: 1000px;
	margin: 0 auto;
}
.wrapper {
	display: table-cell;
	height: 400px;
	vertical-align: middle;
}
.nav {
	margin-top: 40px;
}
.pull-right {
	float: right;
}
a, a:active {
	color: #333;
	text-decoration: none;
}
a:hover {
	color: #999;
}
/* Breadcrups CSS */
.arrow-steps .step {
	font-size: 14px;
	text-align: center;
	color: #666;
	cursor: default;
	margin: 0 3px;
	padding: 10px 10px 10px 30px;
	min-width: 180px;
	float: left;
	position: relative;
	background-color: #d9e3f7;
	-webkit-user-select: none;
	-moz-user-select: none;
	-ms-user-select: none;
	user-select: none;
  transition: background-color 0.2s ease;
}
.arrow-steps .step:after,
.arrow-steps .step:before {
	content: " ";
	position: absolute;
	top: 0;
	right: -17px;
	width: 0;
	height: 0;
	border-top: 27px solid transparent;
	border-bottom: 15px solid transparent;
	border-left: 17px solid #d9e3f7;
	z-index: 2;
  transition: border-color 0.2s ease;
}
.arrow-steps .step:before {
	right: auto;
	left: 0;
	border-left: 17px solid #fff;
	z-index: 0;
}
.arrow-steps .step:first-child:before {
	border: none;
}
.arrow-steps .step:first-child {
	border-top-left-radius: 4px;
	border-bottom-left-radius: 4px;
}
.arrow-steps .step span {
	position: relative;
}
.arrow-steps .step span:before {
	opacity: 0;
	content: "✔";
	position: absolute;
	top: -2px;
	left: -20px;
}
.arrow-steps .step.done span:before {
	opacity: 1;
	-webkit-transition: opacity 0.3s ease 0.5s;
	-moz-transition: opacity 0.3s ease 0.5s;
	-ms-transition: opacity 0.3s ease 0.5s;
	transition: opacity 0.3s ease 0.5s;
}
.arrow-steps .step.current {
	color: #fff;
	background-color: #23468c;
}
.arrow-steps .step.current:after {
	border-left: 17px solid #23468c;
}
.eachStep{
    margin-right:20px;
}
.alignButton{
    margin: 20px;
}
.currentDisplay{
    background-color: #007bff;
}

app.component.html

<div class="container">
  <div class="wrapper">
    <div class="arrow-steps clearfix">
      <div>
        <div class="row " *ngFor="let step of stepsArr">
          <div class="eachStep" *ngFor="let s of step.steps;index as i;">
            <div style="margin:20px">
              <div class="step" [class.current]="i==currentStep">
              <span> step {{i+1}}
                </span>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
    <div *ngFor="let step of stepsArr">
      <div *ngFor="let s of step.steps;index as i">
        <div *ngIf="i==currentStep">
          <app-childcontent [step]="s">
          </app-childcontent>
        </div>
      </div>
      <div class="buttonGroup">
        <button (click)="back(i)" class="btn-primary alignButton">
           Previous</button>
        <button (click)="next(i)" class="btn-primary alignButton ">
          Next</button>
      </div>
    </div>
  </div>
</div>

app.component.ts

import { Component, OnInit } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Step } from 'src/model/step';
import { Elements } from 'src/model/elements';
@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'demoTWO';
  url = 'assets/data.json';
//url="https://reqres.in/api/users";
 private stepsArr:object[];
 step:Step;
 element:Elements;
 currentStep=0;
  constructor(private http:HttpClient){
    }
    ngOnInit(){
      this.http.get<any>(this.url).subscribe(data => {
        console.log(data);
    this.stepsArr = data;
    //console.log(JSON.stringify(this.blockArr));
})
    }
    next(i){
      this.currentStep=this.currentStep+1;
      //alert(this.currentStep);
      if(this.currentStep>2){
      }
    }
    back(i){
      this.currentStep=this.currentStep-1;
      //alert(this.currentStep);
    }
  }

Conclusion

So far, we have seen how to create a progress bar in angular. Angular material also has a stepper component https://material.angular.io/components/stepper/overview. But the stepper component icon is round shape and here we discussed the arrow shape progress bar. I could not create an arrow progress bar using the material stepper component.