feat: add delay input to loading directive

pull/2671/head
mehmet-erim 6 years ago
parent 69a03f1ece
commit ce198e2248

@ -15,6 +15,7 @@
<i class="fa fa-plus mr-1"></i>
<span>{{ 'AbpIdentity::NewRole' | abpLocalization }}</span>
</button>
<button (click)="get()">Refresh</button>
</div>
</div>
</div>
@ -23,6 +24,7 @@
<abp-table
*ngIf="[150, 0] as columnWidths"
[abpLoading]="loading"
[abpLoadingDelay]="500"
[abpTableSort]="{ key: sortKey, order: sortOrder }"
[colgroupTemplate]="tableColGroup"
[headerTemplate]="tableHeader"

@ -31,6 +31,7 @@
<abp-table
*ngIf="[150, 0] as columnWidths"
[abpLoading]="loading"
[abpLoadingDelay]="500"
[abpTableSort]="{ key: sortKey, order: sortOrder }"
[colgroupTemplate]="tableColGroup"
[headerTemplate]="tableHeader"

@ -68,4 +68,8 @@ export default `
.ui-table .ui-table-tbody > tr.empty-row > div.empty-row-content {
border: 1px solid #c8c8c8;
}
.abp-loading {
background: rgba(0, 0, 0, 0.1);
}
`;

@ -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';
<i class="fa fa-spinner fa-pulse abp-spinner"></i>
</div>
`,
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%);

@ -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<any>).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<any>).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<LoadingComponent>;
rootNode: HTMLDivElement;
timerSubscription: Subscription;
constructor(
private elRef: ElementRef<HTMLElement>,
@ -71,4 +93,10 @@ export class LoadingDirective implements OnInit {
}
}
}
ngOnDestroy() {
if (this.timerSubscription) {
this.timerSubscription.unsubscribe();
}
}
}

@ -20,8 +20,8 @@ describe('LoadingDirective', () => {
describe('default', () => {
beforeEach(() => {
spectator = createDirective('<div [abpLoading]="status">Testing Loading Directive</div>', {
hostProps: { status: true },
spectator = createDirective('<div [abpLoading]="loading">Testing Loading Directive</div>', {
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(
'<div [abpLoading]="status" [abpLoadingTargetElement]="target">Testing Loading Directive</div>',
'<div [abpLoading]="loading" [abpLoadingDelay]="delay" [abpLoadingTargetElement]="target">Testing Loading Directive</div>',
{
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('<abp-dummy [abpLoading]="status"></abp-dummy>', {
hostProps: { status: true },
spectator = createDirective('<abp-dummy [abpLoading]="loading"></abp-dummy>', {
hostProps: { loading: true },
});
});
@ -76,7 +87,7 @@ describe('LoadingDirective', () => {
setTimeout(() => {
expect(spectator.directive.targetElement.id).toBe('dummy');
done();
}, 0);
}, 20);
});
});
});

@ -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',
]);

Loading…
Cancel
Save