Skip to content

Instantly share code, notes, and snippets.

@thinhbuzz
Last active December 3, 2019 16:16
Show Gist options
  • Save thinhbuzz/8674d90e5868df7b9d91f0f9de1e1e22 to your computer and use it in GitHub Desktop.
Save thinhbuzz/8674d90e5868df7b9d91f0f9de1e1e22 to your computer and use it in GitHub Desktop.
Angular loading indicator example. Put loader.css file into `src/assets` folder.
import { Component } from '@angular/core';
import { LoaderService } from './loader.service';
import { timer } from 'rxjs';
@Component({
selector: 'app-root',
template: `
<p>
<button (click)="randomTimeout()">Random time out</button>
</p>
`,
styles: []
})
export class AppComponent {
constructor(private loaderService: LoaderService) {
this.loaderService.hide();
}
randomTimeout() {
[this.random(), this.random(), this.random(), this.random(), 500].forEach(item => {
timer(item * 10)
.pipe(this.loaderService.httpLoader())
.subscribe(data => console.log(data));
});
}
random(min = 100, max = 400) {
return Math.floor((Math.random() * (max - min) + min));
}
}
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Angular loader</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
<link href="assets/loader.css" rel="stylesheet" type="text/css"/>
</head>
<body>
<buzz-root></buzz-root>
<div id="loader-container">
<div class="lds-roller">
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
</div>
</div>
</body>
</html>
/*
* loader container
*/
#loader-container {
position: fixed;
width: 100%;
height: 100%;
left: 0;
top: 0;
background: rgba(51, 51, 51, 0.7);
z-index: 999;
display: -ms-flexbox;
display: flex;
-ms-flex-align: center;
align-items: center;
-ms-flex-pack: center;
justify-content: center;
-webkit-animation: loader-fade-in 0.5s;
animation: loader-fade-in 0.5s;
-webkit-animation-fill-mode: backwards;
animation-fill-mode: backwards;
}
@-webkit-keyframes loader-fade-out {
0% {
opacity: 1;
}
99% {
opacity: 0.01;
width: 100%;
height: 100%;
}
100% {
opacity: 0;
width: 0;
height: 0;
}
}
@keyframes loader-fade-out {
0% {
opacity: 1;
}
99% {
opacity: 0.01;
width: 100%;
height: 100%;
}
100% {
opacity: 0;
width: 0;
height: 0;
}
}
@-webkit-keyframes loader-fade-in {
0% {
opacity: 0;
width: 100%;
height: 100%;
}
100% {
opacity: 1;
}
}
@keyframes loader-fade-in {
0% {
opacity: 0;
width: 100%;
height: 100%;
}
100% {
opacity: 1;
}
}
#loader-container.hidden {
-webkit-animation: loader-fade-out 0.5s;
animation: loader-fade-out 0.5s;
-webkit-animation-fill-mode: forwards;
animation-fill-mode: forwards;
}
/*
* end loader container
*/
/*
* loader icon and animation
*/
@-webkit-keyframes loader-lds-roller {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
@keyframes loader-lds-roller {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
#loader-container .lds-roller {
display: inline-block;
position: relative;
width: 80px;
height: 80px;
}
#loader-container .lds-roller div {
-webkit-animation: loader-lds-roller 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;
animation: loader-lds-roller 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;
-ms-transform-origin: 40px 40px;
transform-origin: 40px 40px;
}
#loader-container .lds-roller div:after {
content: " ";
display: block;
position: absolute;
width: 7px;
height: 7px;
border-radius: 50%;
background: #fff;
margin: -4px 0 0 -4px;
}
#loader-container .lds-roller div:nth-child(1) {
-webkit-animation-delay: -0.036s;
animation-delay: -0.036s;
}
#loader-container .lds-roller div:nth-child(1):after {
top: 63px;
left: 63px;
}
#loader-container .lds-roller div:nth-child(2) {
-webkit-animation-delay: -0.072s;
animation-delay: -0.072s;
}
#loader-container .lds-roller div:nth-child(2):after {
top: 68px;
left: 56px;
}
#loader-container .lds-roller div:nth-child(3) {
-webkit-animation-delay: -0.108s;
animation-delay: -0.108s;
}
#loader-container .lds-roller div:nth-child(3):after {
top: 71px;
left: 48px;
}
#loader-container .lds-roller div:nth-child(4) {
-webkit-animation-delay: -0.144s;
animation-delay: -0.144s;
}
#loader-container .lds-roller div:nth-child(4):after {
top: 72px;
left: 40px;
}
#loader-container .lds-roller div:nth-child(5) {
-webkit-animation-delay: -0.18s;
animation-delay: -0.18s;
}
#loader-container .lds-roller div:nth-child(5):after {
top: 71px;
left: 32px;
}
#loader-container .lds-roller div:nth-child(6) {
-webkit-animation-delay: -0.216s;
animation-delay: -0.216s;
}
#loader-container .lds-roller div:nth-child(6):after {
top: 68px;
left: 24px;
}
#loader-container .lds-roller div:nth-child(7) {
-webkit-animation-delay: -0.252s;
animation-delay: -0.252s;
}
#loader-container .lds-roller div:nth-child(7):after {
top: 63px;
left: 17px;
}
#loader-container .lds-roller div:nth-child(8) {
-webkit-animation-delay: -0.288s;
animation-delay: -0.288s;
}
#loader-container .lds-roller div:nth-child(8):after {
top: 56px;
left: 12px;
}
/*
* end loader icon and animation
*/
import { Inject, Injectable, InjectionToken, Optional, Renderer2, RendererFactory2 } from '@angular/core';
import { finalize, switchMap, tap } from 'rxjs/operators';
import { Observable, of } from 'rxjs';
export interface LoaderOptions {
element?: string | HTMLDivElement;
}
export const LOADER_OPTION = new InjectionToken<LoaderOptions>('LoaderOptions');
@Injectable({
providedIn: 'root'
})
export class LoaderService {
appLoadingElement: HTMLDivElement;
renderer: Renderer2;
states: boolean[] = [];
options: LoaderOptions = {
element: 'loader-container'
};
constructor(@Optional() @Inject(LOADER_OPTION) options: LoaderOptions = {},
rendererFactory: RendererFactory2) {
this.renderer = rendererFactory.createRenderer(null, null);
this.initialService(options);
}
show() {
this.states.push(true);
this.checkState();
}
hide() {
this.states.shift();
this.checkState();
}
initialService(options: LoaderOptions) {
this.options = {...this.options, ...(options || {})};
this.appLoadingElement = (this.options.element instanceof HTMLElement ?
this.options.element :
document.getElementById(this.options.element)) as HTMLDivElement;
}
httpLoader() {
return <T>(source: Observable<T>) => of(null)
.pipe(
tap(() => this.show()),
switchMap(() => source.pipe(finalize(() => this.hide())))
);
}
protected checkState() {
if (!this.appLoadingElement) {
return;
}
this.renderer[this.states.length ? 'removeClass' : 'addClass'](this.appLoadingElement, 'hidden');
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment