Angular. How to Introduce Progress Bars in Your Application Using NgRx.

Because we want to not only hide but also replace elements with the progress bar/spinner.

Create a directive in the progress bar module.

ng g d directives/progressWe will use a custom selector without prefix (it violates the Google Style Guide).

Here we subscribe to showProgress for the given progressId and then replace our template with the template from config.

replaceWith.

We define config as an input with the name progressAware.

This is the way to pass values directly to the structural directive.

Let’s add an interface for configuration:import {TemplateRef} from '@angular/core';export interface ProgressConfig { progressId: string; replaceWith: TemplateRef<any>;}Add type for config:@Input('progressAware') config: ProgressConfig;Please don’t forget to unsubscribe from all observables on components/directives destroy.

I usually use takeUntil for this purpose (thanks Ben Lesh).

Add unsubscribe subject:unsubscribe$ = new Subject();Implement onDestroy:ngOnDestroy(): void { this.

unsubscribe$.

next(); this.

unsubscribe$.

complete();}Modify subscription:this.

store.

pipe(select(showProgress() as any, {progressId: this.

config.

progressId}), takeUntil(this.

unsubscribe$))Don’t forget to add the directive to exports in the progress module:.

exports: [ProgressDirective]})Second UseTo do:Introduce structural directive to our first example and get rid of usage progress store in the app componentHow to:Clean up onInit in app.

component.

Modify app.

component template:<input type='button' (click)="onClick()" *progressAware="{progressId: 'login_progress', replaceWith: loading}" value="Start"/><ng-template #loading>Loading.

</ng-template>Here we replace ngIf with theprogressAware structural directive.

We pass id of the progress bar, and which template we should use to replace it when we wait for loading.

The result remains the same, but the code looks slightly better and our brand new directive is easy to use.

However, when I start using this approach I face another use case.

I need to add a spinner to the existing button; I don’t want to replace it.

E.

g.

I have a login button, and when I press on it, I want to have a spinner on top of this button, not instead of this button.

Spinner on top of the buttonWe should support not only the replacement but also the hiding of the progress bar.

Add Hiding BehaviorWe will modify our structural directive.

Let’s modify the configuration in the ProgressConfig and add replace param.

export interface ProgressConfig { progressId: string; replaceWith?: TemplateRef<any>; replace?: boolean;}This param indicates whether we just show/hide the progress, or whether we replace the existing component with progress bars.

2.

Rewrite subscription handling.

Third UseTo do:Add a second “loading” that will appear after clicking the button, and disappears after five seconds.

How to:Let’s modify our app.

component template.

We add replace, true for input, as we will replace it with progress bars only if value is true.

Also we add new loading text, without any params except progressId, as it is mandatory.

Now when you click the button you will see two loading messages.

After five seconds you will see only the button.

Bonus:To do:Create a login button with the progress spinner.

This progress spinner will appear only when we press this button, while the button will become disabled.

As in the picture:Spinner on top of the buttonHow to:Generate a new component.

ng g c login-btn2.

Modify app.

component styles.

3.

Add styles for login button.

4.

Add the button to our component.

<input (click)="onClick()" type='button' class="login" value="Login"/>5.

Add progress bar on the button.

I will use SVG implementation and I will add it right to the login-btn template for the sake of speediness.

<svg xmlns="http://www.

w3.

org/2000/svg" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid" class="progress img lds-eclipse" style="background: none;"> <path stroke="none" d="M10 50A40 40 0 0 0 90 50A40 45 0 0 1 10 50" fill="#5eccb2" transform="rotate(354 49.

9999 52.

4999)"> <animateTransform attributeName="transform" type="rotate" calcMode="linear" values="0 50 52.

5;360 50 52.

5" keyTimes="0;1" dur="1s" begin="0s" repeatCount="indefinite"></animateTransform> </path></svg>6.

The next step is to introduce our progressAware directive.

At this moment you should be comfortable with this.

*progressAware="{progressId: 'login_progress'}"7.

Add click handler for the buttononClick() { this.

store.

dispatch(new LoginAction()); setTimeout(() => this.

store.

dispatch(new LoginSuccessAction()), 5000);}Now, everything works correctly except disabling the button and hiding the text.

Of course we can manually add some class in the onClick event but it will be difficult to track the moment when the process ends.

We will slightly modify our structural directive by introducing lifecycle hooks: onShow and onHide8.

Modifying the structural directive.

Add new fields to theProgressConfig interface.

onShow?: () => void; onHide?: () => void;Update subscription handling.

9.

Update login-btn component by adding the handling of onShow and onHide events.

Add a new property to bind the input class with, and two methods to handle the hooks.

Update the template:Update styles to support the new submitting class:&.

submitting { color: #00000000; opacity: 0.

1; pointer-events: none;}ResultFinallyThank you for reading this article.

I hope it was useful.

Don’t hesitate to ask questions and leave comments.

.

. More details

Leave a Reply