diff --git a/npm/ng-packs/packages/identity/src/lib/components/roles/roles.component.html b/npm/ng-packs/packages/identity/src/lib/components/roles/roles.component.html index 57858686fa..32ade3186c 100644 --- a/npm/ng-packs/packages/identity/src/lib/components/roles/roles.component.html +++ b/npm/ng-packs/packages/identity/src/lib/components/roles/roles.component.html @@ -15,6 +15,7 @@ {{ 'AbpIdentity::NewRole' | abpLocalization }} + @@ -23,6 +24,7 @@ tr.empty-row > div.empty-row-content { border: 1px solid #c8c8c8; } + +.abp-loading { + background: rgba(0, 0, 0, 0.1); +} `; diff --git a/npm/ng-packs/packages/theme-shared/src/lib/components/loading/loading.component.ts b/npm/ng-packs/packages/theme-shared/src/lib/components/loading/loading.component.ts index 0271ced29b..c68757b4a0 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/components/loading/loading.component.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/components/loading/loading.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, OnInit, ViewEncapsulation } from '@angular/core'; @Component({ selector: 'abp-loading', @@ -7,10 +7,10 @@ import { Component, OnInit } from '@angular/core'; `, + encapsulation: ViewEncapsulation.None, styles: [ ` .abp-loading { - background: rgba(0, 0, 0, 0.2); position: absolute; width: 100%; height: 100%; @@ -23,6 +23,7 @@ import { Component, OnInit } from '@angular/core'; position: absolute; top: 50%; left: 50%; + font-size: 14px; -moz-transform: translateX(-50%) translateY(-50%); -o-transform: translateX(-50%) translateY(-50%); -ms-transform: translateX(-50%) translateY(-50%); diff --git a/npm/ng-packs/packages/theme-shared/src/lib/directives/loading.directive.ts b/npm/ng-packs/packages/theme-shared/src/lib/directives/loading.directive.ts index d38b3713b7..40b76cbfc8 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/directives/loading.directive.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/directives/loading.directive.ts @@ -1,22 +1,23 @@ import { - Directive, - ElementRef, - AfterViewInit, - ViewContainerRef, ComponentFactoryResolver, - Input, - Injector, ComponentRef, - ComponentFactory, - HostBinding, + Directive, + ElementRef, EmbeddedViewRef, - Renderer2, + HostBinding, + Injector, + Input, OnInit, + OnDestroy, + Renderer2, + ViewContainerRef, } from '@angular/core'; +import { Subscription, timer } from 'rxjs'; +import { take } from 'rxjs/operators'; import { LoadingComponent } from '../components/loading/loading.component'; @Directive({ selector: '[abpLoading]' }) -export class LoadingDirective implements OnInit { +export class LoadingDirective implements OnInit, OnDestroy { private _loading: boolean; @HostBinding('style.position') @@ -29,29 +30,50 @@ export class LoadingDirective implements OnInit { set loading(newValue: boolean) { setTimeout(() => { - if (!this.componentRef) { - this.componentRef = this.cdRes - .resolveComponentFactory(LoadingComponent) - .create(this.injector); - } + if (!newValue && this.timerSubscription) { + this.timerSubscription.unsubscribe(); + this.timerSubscription = null; + this._loading = newValue; - if (newValue && !this.rootNode) { - this.rootNode = (this.componentRef.hostView as EmbeddedViewRef).rootNodes[0]; - this.targetElement.appendChild(this.rootNode); - } else { - this.renderer.removeChild(this.rootNode.parentElement, this.rootNode); - this.rootNode = null; + if (this.rootNode) { + this.renderer.removeChild(this.rootNode.parentElement, this.rootNode); + this.rootNode = null; + } + return; } - this._loading = newValue; + this.timerSubscription = timer(this.delay) + .pipe(take(1)) + .subscribe(() => { + if (!this.componentRef) { + this.componentRef = this.cdRes + .resolveComponentFactory(LoadingComponent) + .create(this.injector); + } + + if (newValue && !this.rootNode) { + this.rootNode = (this.componentRef.hostView as EmbeddedViewRef).rootNodes[0]; + this.targetElement.appendChild(this.rootNode); + } else { + this.renderer.removeChild(this.rootNode.parentElement, this.rootNode); + this.rootNode = null; + } + + this._loading = newValue; + this.timerSubscription = null; + }); }, 0); } @Input('abpLoadingTargetElement') targetElement: HTMLElement; + @Input('abpLoadingDelay') + delay = 0; + componentRef: ComponentRef; rootNode: HTMLDivElement; + timerSubscription: Subscription; constructor( private elRef: ElementRef, @@ -71,4 +93,10 @@ export class LoadingDirective implements OnInit { } } } + + ngOnDestroy() { + if (this.timerSubscription) { + this.timerSubscription.unsubscribe(); + } + } } diff --git a/npm/ng-packs/packages/theme-shared/src/lib/tests/loading.directive.spec.ts b/npm/ng-packs/packages/theme-shared/src/lib/tests/loading.directive.spec.ts index 1b49b8581c..b359a2000c 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/tests/loading.directive.spec.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/tests/loading.directive.spec.ts @@ -20,8 +20,8 @@ describe('LoadingDirective', () => { describe('default', () => { beforeEach(() => { - spectator = createDirective('
Testing Loading Directive
', { - hostProps: { status: true }, + spectator = createDirective('
Testing Loading Directive
', { + hostProps: { loading: true }, }); }); @@ -30,7 +30,7 @@ describe('LoadingDirective', () => { expect(spectator.directive.rootNode).toBeTruthy(); expect(spectator.directive.componentRef).toBeTruthy(); done(); - }, 0); + }, 20); }); }); @@ -40,9 +40,9 @@ describe('LoadingDirective', () => { beforeEach(() => { spectator = createDirective( - '
Testing Loading Directive
', + '
Testing Loading Directive
', { - hostProps: { status: true, target: mockTarget }, + hostProps: { loading: true, target: mockTarget, delay: 0 }, }, ); }); @@ -51,24 +51,35 @@ describe('LoadingDirective', () => { setTimeout(() => { expect(spy).toHaveBeenCalled(); done(); - }, 0); + }, 20); }); it('should remove the loading component to the DOM', done => { const rendererSpy = jest.spyOn(spectator.directive['renderer'], 'removeChild'); - spectator.setHostInput({ status: false }); + setTimeout(() => spectator.setHostInput({ loading: false }), 0); setTimeout(() => { expect(rendererSpy).toHaveBeenCalled(); expect(spectator.directive.rootNode).toBeFalsy(); done(); - }, 0); + }, 20); + }); + + it('should appear with delay', done => { + spectator.setHostInput({ loading: false, delay: 20 }); + spectator.detectChanges(); + setTimeout(() => spectator.setHostInput({ loading: true }), 0); + setTimeout(() => expect(spectator.directive.loading).toBe(false), 15); + setTimeout(() => { + expect(spectator.directive.loading).toBe(true); + done(); + }, 50); }); }); describe('with a component selector', () => { beforeEach(() => { - spectator = createDirective('', { - hostProps: { status: true }, + spectator = createDirective('', { + hostProps: { loading: true }, }); }); @@ -76,7 +87,7 @@ describe('LoadingDirective', () => { setTimeout(() => { expect(spectator.directive.targetElement.id).toBe('dummy'); done(); - }, 0); + }, 20); }); }); }); diff --git a/npm/ng-packs/packages/theme-shared/src/lib/tests/toaster.service.spec.ts b/npm/ng-packs/packages/theme-shared/src/lib/tests/toaster.service.spec.ts index d7fbd7219e..23e99b8204 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/tests/toaster.service.spec.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/tests/toaster.service.spec.ts @@ -41,7 +41,7 @@ describe('ToasterService', () => { expect(spectator.query('div.toast')).toBeTruthy(); expect(spectator.query('.toast-icon i')).toHaveClass('fa-times-circle'); expect(spectator.query('div.toast-title')).toHaveText('title'); - expect(spectator.query('div.toast-message')).toHaveText('test'); + expect(spectator.query('p.toast-message')).toHaveText('test'); }); test('should display a warning toast', () => { @@ -71,7 +71,7 @@ describe('ToasterService', () => { 'summary1', 'summary2', ]); - expect(spectator.queryAll('div.toast-message').map(node => node.textContent.trim())).toEqual([ + expect(spectator.queryAll('p.toast-message').map(node => node.textContent.trim())).toEqual([ 'detail1', 'detail2', ]);