Creating an Angular Component Library - Progress Bar Component
Published on
Another common component you might use in an application is a progress bar. There are a few types of progress bars one being the general bootstrap style. Another one that I’m starting to see more frequently is the style positioned to the top of the page with an animation spanning the full width of the page similar to YouTube. This is the type we’re going to create.
Prerequisites
- Install Node
- Install Angular CLI
- Creating an Angular Component Library - Workspace Setup
- OR Clone this github repository
Generating Our Progress Bar Component
Our progress bar component will comprise a 3 different items. First, we will need a module just like our Alert component. We will also need a component for our progress bar. And finally we will need a service to allow us to update our root level progress bar from other components.
ng generate module components/progress-bar --project=foo
ng generate component components/progress-bar --project=foo
ng generate service components/progress-bar/progress-bar --project=foo
Now that we have our generated items, we now need to update the module to export our component.
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ProgressBarComponent } from './progress-bar.component';
@NgModule({
declarations: [
ProgressBarComponent
],
imports: [
CommonModule
],
exports: [
ProgressBarComponent
]
})
export class ProgressBarModule { }
One additional file we are going to create is an enum to hold our configurable fill color.
touch projects/foo/src/lib/components/progress-bar/progress-bar-fill.enum.ts
And our enum…
export enum ProgressBarFill {
PRIMARY = 'progress-bar-fill-primary',
SECONDARY = 'progress-bar-fill-secondary',
SUCCESS = 'progress-bar-fill-success',
INFO = 'progress-bar-fill-info',
WARNING = 'progress-bar-fill-warning',
DANGER = 'progress-bar-fill-danger'
}
And finally we will update our components
directory’s public_api.ts
file, exporting our module, component, service, and enum.
// Progress Bar Component
export * from './progress-bar/progress-bar.module';
export * from './progress-bar/progress-bar.component';
export * from './progress-bar/progress-bar.service';
export * from './progress-bar/progress-bar-fill.enum';
Creating Our Progress Bar Component
Again, like our alert component, our progress bar is a very simple HTML structure with a two nested divs. The outer div will be a container and the inner div will be the fill.
<div class="progress-bar">
<div class="progress-bar-fill"
[ngClass]="fill"
[ngStyle]="{ width: (progressValue$ | async) + '%' }">
</div>
</div>
Our SCSS styling will position our progress bar to the top of the page. We’re also creating fill color classes based on our enum values…
@import '../_shared/scss/variables';
.progress-bar {
position: fixed;
background: transparent;
height: 8px;
top: 0;
left: 0;
right: 0;
z-index: 200;
.progress-bar-fill {
height: 100%;
transition: width 500ms;
&.progress-bar-fill-primary {
background: $primary;
}
&.progress-bar-fill-secondary {
background: $secondary;
}
&.progress-bar-fill-success {
background: $success;
}
&.progress-bar-fill-info {
background: $info;
}
&.progress-bar-fill-warning {
background: $warning;
}
&.progress-bar-fill-danger {
background: $danger;
}
}
}
And our component class file…
import { Component, OnInit, Input, ChangeDetectionStrategy } from '@angular/core';
import { Observable } from 'rxjs';
import { ProgressBarFill } from './progress-bar-fill.enum';
import { ProgressBarService } from './progress-bar.service';
@Component({
selector: 'foo-progress-bar',
templateUrl: './progress-bar.component.html',
styleUrls: ['./progress-bar.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ProgressBarComponent implements OnInit {
@Input()
public fill: ProgressBarFill;
public progressValue$: Observable<number>;
constructor(private _progressBarService: ProgressBarService) {
this.fill = ProgressBarFill.PRIMARY;
}
ngOnInit(): void {
this.progressValue$ = this._progressBarService.onProgressChange()
}
}
Our component class will have one @Input()
value, our fill color. There is very minimal logic in our component. We are using our progress bar service to subscribe to progress changes. When we receive a new value, it is set to progressValue
. The progressValue
is used in our template with [ngStyle]
to set the width of the fill div.
Creating Our Progress Bar Service
Our progress bar service is what drives our component’s functionality. Our service will maintain the current progress of the progress bar broadcast two events. One event is to notify subscribers of progress value changes and the other broadcasts that progress is complete.
import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable, Subject } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class ProgressBarService {
private _progressValue: number;
private _progressSource: BehaviorSubject<number>;
private _progressCompleteSource: Subject<boolean>;
constructor() {
this._progressValue = 0;
this._progressSource = new BehaviorSubject<number>(this._progressValue);
this._progressCompleteSource = new Subject<boolean>();
}
public updateProgress(value: number): void {
this._progressValue = value;
this._progressSource.next(this._progressValue);
if (value >= 100) {
setTimeout(() => {
this._progressCompleteSource.next(true);
this._progressValue = 0;
this._progressSource.next(this._progressValue);
}, 1000);
}
}
public resetProgress(): void {
this._progressValue = 0;
this._progressSource.next(this._progressValue);
}
public onProgressChange(): Observable<number> {
return this._progressSource.asObservable();
}
public onProgressComplete(): Observable<boolean> {
return this._progressCompleteSource.asObservable();
}
}
So how are we broadcasting our events? We’re using an RxJS Subject
and a BehaviorSubject
. These both follow the publish subscribe ,patter, observer pattern, which allow our components to subscribe to changes. When we want to broadcast/emit a new value, we simply call next()
on our subject with the new value. The subject will emit the new value to all of it’s subscribers.
The main difference between Subject
and BehaviorSubject
is that the later is givin an initial value. The later also maintains the last value emitted so when a new subscribion is established, it will immediately receive thew last emitted value.
We also have to public method to update and reset the progress and that’s pretty much it. We can place a single instance of our component at the root of our project and interact with it from any other component by inject our progress bar service.
Using Our Progress Bar
We will now create a quick demo of our progress bar. Our demo will manipulate our progress bar using our service. We will also subscribe to progress complete events and show one of our Alert components stating progress is complete.
First will update our app component html
<div class="container">
<div>
<foo-alert
[type]="alertType"
[visible]="alertIsVisible"
[message]="alertMessage"
[iconClass]="alertIconClass"
[dismissible]="alertIsDismissible">
</foo-alert>
</div>
</div>
<foo-progress-bar [fill]="ProgressBarFill.DANGER"></foo-progress-bar>
And our app component class
import { Component, OnInit } from '@angular/core';
import { take } from 'rxjs/operators';
import { AlertType, ProgressBarFill, ProgressBarService } from 'foo';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {
public ProgressBarFill = ProgressBarFill;
public alertType: AlertType;
public alertIsVisible: boolean;
public alertMessage: string;
public alertIconClass: string;
public alertIsDismissible: boolean
constructor(private _progressBarService: ProgressBarService) {
this.alertType = AlertType.INFO;
this.alertIsDismissible = false;
this.alertMessage = 'Progress complete';
this.alertIconClass = 'fas fa-info-circle';
this.alertIsDismissible = false;
}
ngOnInit() {
this._progressBarService.updateProgress(25);
setTimeout(() => this._progressBarService.updateProgress(50), 2000);
setTimeout(() => this._progressBarService.updateProgress(100), 3000);
this._progressBarService.onProgressComplete()
.pipe(take(1))
.subscribe(complete => {
if (complete) {
this.alertIsVisible = true;
setTimeout(() => this.alertIsVisible = false, 2000);
}
});
}
}
Now we just need to build our library and run our demo project.
npm run build
npm start
Stackblitz Result
I’ve also embeded a Stackblitz showing our progress bar with our dashabord layout we create a few posts back. If you click “Click To Update” you will see our progress bar in action. After progress bar completes, our alert component will display a completion message.
The completed github repository can be found here
More Posts
- Creating an Angular Component Library - Workspace Setup
- Creating an Angular Component Library - Alert Component
- Creating an Angular Component Library - Progress Bar Component
- Creating an Angular Component Library - Toaster Component
- Creating an Angular Component Library - Card Component
- Creating an Angular Component Library - Flip Card Component
- Creating an Angular Component Library - Overlay Loader Component
- Creating an Angular Component Library - Overlay Side Panel Component
- Creating an Angular Component Library - Button Component
- Creating an Angular Component Library - Toggle Switch Component
- Creating an Angular Component Library - Checkbox Component