ng-busy
Version:
Show busy/loading indicators on any promise or subscription
437 lines (425 loc) • 20.9 kB
JavaScript
import * as i0 from '@angular/core';
import { Injectable, Component, Inject, Optional, EventEmitter, Injector, TemplateRef, Directive, Input, Output, NgModule } from '@angular/core';
import * as i1 from '@angular/common';
import { CommonModule } from '@angular/common';
import { Subject, BehaviorSubject, takeUntil, concatAll, take, timer, map, combineLatest, filter, from, tap, Observable, skip, distinctUntilChanged, Subscription } from 'rxjs';
import { style, trigger, transition, animate } from '@angular/animations';
class InstanceConfigHolderService {
constructor() { }
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: InstanceConfigHolderService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: InstanceConfigHolderService, providedIn: 'any' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: InstanceConfigHolderService, decorators: [{
type: Injectable,
args: [{
providedIn: 'any'
}]
}], ctorParameters: function () { return []; } });
class BusyConfig {
constructor(config = {}) {
for (const option of Object.keys(BUSY_CONFIG_DEFAULTS)) {
this[option] = config[option] !== undefined ? config[option] : BUSY_CONFIG_DEFAULTS[option];
}
}
}
class DefaultBusyComponent {
constructor(instanceConfigHolder) {
this.instanceConfigHolder = instanceConfigHolder;
}
get message() {
return this.instanceConfigHolder.config.message;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: DefaultBusyComponent, deps: [{ token: 'instanceConfigHolder' }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.1.1", type: DefaultBusyComponent, selector: "default-busy", ngImport: i0, template: `
<div class="ng-busy-default-wrapper">
<div class="ng-busy-default-sign">
<div class="ng-busy-default-spinner">
<div class="bar1"></div>
<div class="bar2"></div>
<div class="bar3"></div>
<div class="bar4"></div>
<div class="bar5"></div>
<div class="bar6"></div>
<div class="bar7"></div>
<div class="bar8"></div>
<div class="bar9"></div>
<div class="bar10"></div>
<div class="bar11"></div>
<div class="bar12"></div>
</div>
<div class="ng-busy-default-text">{{message}}</div>
</div>
</div>
`, isInline: true }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: DefaultBusyComponent, decorators: [{
type: Component,
args: [{
selector: 'default-busy',
template: `
<div class="ng-busy-default-wrapper">
<div class="ng-busy-default-sign">
<div class="ng-busy-default-spinner">
<div class="bar1"></div>
<div class="bar2"></div>
<div class="bar3"></div>
<div class="bar4"></div>
<div class="bar5"></div>
<div class="bar6"></div>
<div class="bar7"></div>
<div class="bar8"></div>
<div class="bar9"></div>
<div class="bar10"></div>
<div class="bar11"></div>
<div class="bar12"></div>
</div>
<div class="ng-busy-default-text">{{message}}</div>
</div>
</div>
`,
}]
}], ctorParameters: function () { return [{ type: InstanceConfigHolderService, decorators: [{
type: Inject,
args: ['instanceConfigHolder']
}] }]; } });
const BUSY_CONFIG_DEFAULTS = {
template: DefaultBusyComponent,
templateNgStyle: {},
delay: 0,
minDuration: 0,
backdrop: true,
message: 'Please wait...',
wrapperClass: 'ng-busy',
disableAnimation: false
};
function isPromise(value) {
return value && typeof value.subscribe !== 'function' && typeof value.then === 'function';
}
class BusyTrackerService {
get isActive() {
return this.active.value;
}
get busyList() {
return [...this.busyQueue];
}
get isBusy() {
return this.busyQueue.filter(b => !b.closed).length > 0;
}
constructor() {
this.busyQueue = [];
this.operations = new Subject();
this.busyDone = new Subject();
this.destroyIndicator = new Subject();
this.checkSubject = new Subject();
this.processingIndicator = new BehaviorSubject(false);
this.active = new BehaviorSubject(false);
this.reset();
this.operations.pipe(takeUntil(this.destroyIndicator), concatAll()).subscribe();
this.checkSubject.pipe(takeUntil(this.destroyIndicator)).subscribe((options) => {
this.processingIndicator.pipe(take(1)).subscribe((isProcessing) => {
if (isProcessing === false && this.isBusy) {
this.processingIndicator.next(true);
timer(options.delay || 0).pipe(map(() => this.isBusy)).subscribe((stillBusy) => {
if (stillBusy) {
this.active.next(stillBusy);
combineLatest([this.busyDone, timer(options.minDuration || 0)])
.pipe(takeUntil(this.active.pipe(filter(a => a === false))))
.subscribe(() => {
if (!this.isBusy) {
this.reset();
}
});
}
else {
this.processingIndicator.next(false);
}
});
}
});
});
}
load(options) {
this.operations.next(from(options.busyList).pipe(filter(busy => busy !== null && busy !== undefined && !busy.hasOwnProperty('__loaded_mark_by_ng_busy')), tap((busy) => Object.defineProperty(busy, '__loaded_mark_by_ng_busy', {
value: true, configurable: false, enumerable: false, writable: false
})), map((busy) => isPromise(busy) ? from(busy).subscribe() : busy), tap(subscription => this.appendToQueue(subscription))));
this.checkSubject.next(options);
}
updateActiveStatus() {
this.busyQueue = this.busyQueue.filter((cur) => cur && !cur.closed);
if (this.busyQueue.length === 0) {
this.busyDone.next(true);
}
}
reset() {
this.active.next(false);
this.busyQueue = [];
this.processingIndicator.next(false);
}
appendToQueue(busy) {
this.busyQueue.push(busy);
busy.add(() => {
this.operations.next(new Observable((subscriber) => {
this.updateActiveStatus();
subscriber.complete();
}));
});
}
ngOnDestroy() {
this.destroyIndicator.next(null);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: BusyTrackerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: BusyTrackerService, providedIn: 'any' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: BusyTrackerService, decorators: [{
type: Injectable,
args: [{
providedIn: 'any'
}]
}], ctorParameters: function () { return []; } });
class BusyConfigHolderService {
constructor(config) {
this.config = Object.assign({}, BUSY_CONFIG_DEFAULTS, config || new BusyConfig());
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: BusyConfigHolderService, deps: [{ token: BusyConfig, optional: true }], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: BusyConfigHolderService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: BusyConfigHolderService, decorators: [{
type: Injectable,
args: [{
providedIn: 'root'
}]
}], ctorParameters: function () { return [{ type: BusyConfig, decorators: [{
type: Optional
}] }]; } });
const inactiveStyle = style({
opacity: 0,
transform: 'translateY(-40px)'
});
const timing = '.3s ease';
class NgBusyComponent {
constructor(instanceConfigHolder, busyEmitter, vcr, cdr) {
this.instanceConfigHolder = instanceConfigHolder;
this.busyEmitter = busyEmitter;
this.vcr = vcr;
this.cdr = cdr;
this.disableAnimation = false;
this.showBackdrop = true;
this.destroyIndicator = new Subject();
this.show = new Subject();
this.show.pipe(takeUntil(this.destroyIndicator)).subscribe(() => {
this.cdr.detectChanges();
});
this.busyEmitter.pipe(takeUntil(this.destroyIndicator))
.subscribe((isActive) => {
if (isActive === true) {
const config = this.instanceConfigHolder.config;
this.wrapperClass = config.wrapperClass;
this.showBackdrop = config.backdrop;
this.disableAnimation = config.disableAnimation;
}
this.show.next(isActive);
});
}
ngOnDestroy() {
this.destroyIndicator.next(null);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: NgBusyComponent, deps: [{ token: 'instanceConfigHolder' }, { token: 'busyEmitter' }, { token: i0.ViewContainerRef }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.1.1", type: NgBusyComponent, selector: "lib-ng-busy", ngImport: i0, template: "<ng-container *ngIf=\"show | async\">\r\n <div [class]=\"wrapperClass\" @flyInOut [@.disabled]=\"disableAnimation\">\r\n <ng-content></ng-content>\r\n </div>\r\n <div class=\"ng-busy-backdrop\" @flyInOut [@.disabled]=\"disableAnimation\" *ngIf=\"showBackdrop\">\r\n </div>\r\n</ng-container>\r\n", styles: [""], dependencies: [{ kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: i1.AsyncPipe, name: "async" }], animations: [
trigger('flyInOut', [
transition('void => *', [
inactiveStyle,
animate(timing)
]),
transition('* => void', [
animate(timing, inactiveStyle)
])
])
] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: NgBusyComponent, decorators: [{
type: Component,
args: [{ selector: 'lib-ng-busy', animations: [
trigger('flyInOut', [
transition('void => *', [
inactiveStyle,
animate(timing)
]),
transition('* => void', [
animate(timing, inactiveStyle)
])
])
], template: "<ng-container *ngIf=\"show | async\">\r\n <div [class]=\"wrapperClass\" @flyInOut [@.disabled]=\"disableAnimation\">\r\n <ng-content></ng-content>\r\n </div>\r\n <div class=\"ng-busy-backdrop\" @flyInOut [@.disabled]=\"disableAnimation\" *ngIf=\"showBackdrop\">\r\n </div>\r\n</ng-container>\r\n" }]
}], ctorParameters: function () { return [{ type: InstanceConfigHolderService, decorators: [{
type: Inject,
args: ['instanceConfigHolder']
}] }, { type: i0.EventEmitter, decorators: [{
type: Inject,
args: ['busyEmitter']
}] }, { type: i0.ViewContainerRef }, { type: i0.ChangeDetectorRef }]; } });
class NgBusyDirective {
set options(op) {
this._option = op;
}
get options() {
return this._option;
}
get trackerService() {
return this.tracker;
}
constructor(configHolder, instanceConfigHolder, tracker, cdr, vcr, renderer, injector) {
this.configHolder = configHolder;
this.instanceConfigHolder = instanceConfigHolder;
this.tracker = tracker;
this.cdr = cdr;
this.vcr = vcr;
this.renderer = renderer;
this.injector = injector;
this.busyStart = new EventEmitter();
this.busyStop = new EventEmitter();
this.destroyIndicator = new Subject();
this.configLoader = new Subject();
this.busyEmitter = new EventEmitter();
tracker.active.pipe(skip(1), takeUntil(this.destroyIndicator), distinctUntilChanged()).subscribe((status) => {
if (status === true) {
this.recreateBusyIfNecessary();
this.busyStart.emit();
}
else {
this.busyStop.emit();
}
this.busyEmitter.next(status);
});
this.configLoader.pipe(takeUntil(this.destroyIndicator)).subscribe((config) => {
const busyList = config.busy.filter(b => b?.['closed'] !== true && !b?.hasOwnProperty?.('__loaded_mark_by_ng_busy'));
this.optionsNorm = config;
this.instanceConfigHolder.config = this.optionsNorm;
if (busyList.length > 0) {
this.tracker.load({
busyList, delay: this.optionsNorm.delay,
minDuration: this.optionsNorm.minDuration
});
}
});
}
ngDoCheck() {
this.configLoader.next(this.normalizeOptions(this.options));
}
ngOnDestroy() {
this.destroyIndicator.next(null);
}
recreateBusyIfNecessary() {
this.destroyComponents();
this.template = this.optionsNorm.template;
this.templateNgStyle = this.optionsNorm.templateNgStyle;
this.createBusy();
}
normalizeOptions(options) {
if (!options) {
options = { busy: [] };
}
else if (Array.isArray(options)) {
options = { busy: options };
}
else if (isPromise(options) || options instanceof Subscription) {
options = { busy: [options] };
}
options = Object.assign({}, this.configHolder.config, options);
if (!Array.isArray(options.busy)) {
options.busy = [options.busy];
}
return options;
}
destroyComponents() {
if (this.busyRef) {
this.busyRef.destroy();
}
}
createBusy() {
const injector = Injector.create({
providers: [
{
provide: 'instanceConfigHolder',
useValue: this.instanceConfigHolder
},
{
provide: 'busyEmitter',
useValue: this.busyEmitter
}
], parent: this.injector
});
this.template = this.optionsNorm.template;
this.busyRef = this.vcr.createComponent(NgBusyComponent, { injector, projectableNodes: this.generateNgContent(injector) });
this.busyRef.onDestroy(() => {
this.busyRef.instance.ngOnDestroy();
});
this.cdr.markForCheck();
this.busyRef.hostView.detectChanges();
}
generateNgContent(injector) {
if (typeof this.template === 'string') {
const element = this.renderer.createText(this.template);
return [[element]];
}
if (this.template instanceof TemplateRef) {
const context = {};
const viewRef = this.template.createEmbeddedView(context);
return [viewRef.rootNodes];
}
if (typeof this.template === 'function') {
const factory = this.vcr.createComponent(this.template, { injector });
factory.onDestroy(() => {
factory?.instance?.ngOnDestroy?.();
});
factory.changeDetectorRef.markForCheck();
return [[factory.location.nativeElement]];
}
return [[]];
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: NgBusyDirective, deps: [{ token: BusyConfigHolderService }, { token: InstanceConfigHolderService }, { token: BusyTrackerService }, { token: i0.ChangeDetectorRef }, { token: i0.ViewContainerRef }, { token: i0.Renderer2 }, { token: i0.Injector }], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.1.1", type: NgBusyDirective, selector: "[ngBusy]", inputs: { options: ["ngBusy", "options"] }, outputs: { busyStart: "busyStart", busyStop: "busyStop" }, providers: [BusyTrackerService, InstanceConfigHolderService], exportAs: ["ngBusy"], ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: NgBusyDirective, decorators: [{
type: Directive,
args: [{
selector: '[ngBusy]',
providers: [BusyTrackerService, InstanceConfigHolderService],
exportAs: 'ngBusy'
}]
}], ctorParameters: function () { return [{ type: BusyConfigHolderService }, { type: InstanceConfigHolderService }, { type: BusyTrackerService }, { type: i0.ChangeDetectorRef }, { type: i0.ViewContainerRef }, { type: i0.Renderer2 }, { type: i0.Injector }]; }, propDecorators: { options: [{
type: Input,
args: ['ngBusy']
}], busyStart: [{
type: Output
}], busyStop: [{
type: Output
}] } });
class NgBusyModule {
static forRoot(config) {
return {
ngModule: NgBusyModule,
providers: [
{ provide: BusyConfig, useValue: config }
]
};
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: NgBusyModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "16.1.1", ngImport: i0, type: NgBusyModule, declarations: [DefaultBusyComponent, NgBusyDirective, NgBusyComponent], imports: [CommonModule], exports: [NgBusyDirective] }); }
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: NgBusyModule, providers: [BusyConfigHolderService, BusyTrackerService], imports: [CommonModule] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: NgBusyModule, decorators: [{
type: NgModule,
args: [{
imports: [CommonModule],
declarations: [DefaultBusyComponent, NgBusyDirective, NgBusyComponent],
providers: [BusyConfigHolderService, BusyTrackerService],
exports: [NgBusyDirective]
}]
}] });
/*
* Public API Surface of ng-busy
*/
/**
* Generated bundle index. Do not edit.
*/
export { BUSY_CONFIG_DEFAULTS, BusyConfig, BusyTrackerService, DefaultBusyComponent, InstanceConfigHolderService, NgBusyComponent, NgBusyDirective, NgBusyModule, isPromise };
//# sourceMappingURL=ng-busy.mjs.map