Just about every project I need to have at least one loading spinner to indicate to the user that data is being loaded and to wait. My recent project I found that to be no exception. I wanted to come up with something that I thought was a little nicer than what I have used in the past.
To give credit where credit is due this was inspired by @divyamember with this stackblitz. Here is my take:
import {Injectable, TemplateRef, ViewContainerRef} from '@angular/core';
import {Overlay, OverlayRef, PositionStrategy} from "@angular/cdk/overlay";
import {TemplatePortal} from "@angular/cdk/portal";
@Injectable({
providedIn: 'root'
})
export class OverlayService {
constructor(
private overlay: Overlay
) { }
createOverlay(config: any): OverlayRef {
return this.overlay.create(config);
}
attachTemplatePortal(overlay: OverlayRef, template: TemplateRef<any>, view: ViewContainerRef) {
let templatePortal = new TemplatePortal(template, view);
overlay.attach(templatePortal);
}
positionGloballyCenter(): PositionStrategy {
return this.overlay.position()
.global()
.centerHorizontally()
.centerVertically();
}
}
My OverlayService, with not real significant changes. Three methods used to setup the overlay.
import {Component, DoCheck, Input, OnInit, TemplateRef, ViewChild, ViewContainerRef} from '@angular/core';
import {OverlayService} from "../../services/overlay.service";
import {OverlayRef} from "@angular/cdk/overlay";
@Component({
selector: 'app-loading-spinner',
templateUrl: './loading-spinner.component.html',
styleUrls: ['./loading-spinner.component.scss']
})
export class LoadingSpinnerComponent implements OnInit,DoCheck {
@Input("loading") loading: boolean = true;
@Input("message") message: string = '';
@ViewChild('spinnerRef') private spinnerRef: TemplateRef<any>;
config:any ={};
private overlay: OverlayRef;
constructor(private vcRef: ViewContainerRef, private overlayService: OverlayService) { }
ngOnInit(): void {
this.config = {
hasBackdrop: true,
positionStrategy: this.overlayService.positionGloballyCenter()
};
this.overlay = this.overlayService.createOverlay(this.config);
}
ngDoCheck(): void {
if (this.loading && !this.overlay.hasAttached() && this.spinnerRef != undefined) {
this.overlayService.attachTemplatePortal(this.overlay, this.spinnerRef, this.vcRef);
} else if (!this.loading && this.overlay.hasAttached()) {
this.overlay.detach();
}
}
}
Here is my code for my component. Now I removed many of the inputs, and broke this down to just what I needed. An input to indicate if we are still loading, and the message to indicate what we are doing.
.center-content {
display:flex;
justify-content:center;
align-items:center;
}
This is my scss to center the content in my HTML.
<ng-template #spinnerRef>
<mat-card>
<mat-card-header>
<mat-card-title>
{{message}}
</mat-card-title>
</mat-card-header>
<mat-card-content class="center-content">
<mat-spinner [color]="'warn'">
</mat-spinner>
</mat-card-content>
</mat-card>
</ng-template>
<ng-content></ng-content>
This will allow the user to see the page as it is built, but keep them from interacting with it until it has completed loading, while showing a nicely formatted mat-card with a spinner that the application is loading.
<app-loading-spinner [loading]="true" message="Loading Data">
<p>Waiting for data to load</p>
</app-loading-spinner>
Simple usage your wrapped HTML will be loaded, and a Spinner with message “Loading Data”