Last active
December 3, 2019 16:16
-
-
Save thinhbuzz/8674d90e5868df7b9d91f0f9de1e1e22 to your computer and use it in GitHub Desktop.
Angular loading indicator example. Put loader.css file into `src/assets` folder.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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 | |
*/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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