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
new file mode 100644
index 00000000000..6c2201e940f
--- /dev/null
+++ b/npm/ng-packs/packages/theme-shared/src/lib/components/loading/loading.component.ts
@@ -0,0 +1,39 @@
+import { Component, OnInit } from '@angular/core';
+
+@Component({
+ selector: 'abp-loading',
+ template: `
+
+
+
+ `,
+ styles: [
+ `
+ .abp-loading {
+ background: rgba(0, 0, 0, 0.3);
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ top: 0;
+ left: 0;
+ z-index: 1040;
+ }
+
+ .abp-loading .abp-spinner {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ -moz-transform: translateX(-50%) translateY(-50%);
+ -o-transform: translateX(-50%) translateY(-50%);
+ -ms-transform: translateX(-50%) translateY(-50%);
+ -webkit-transform: translateX(-50%) translateY(-50%);
+ transform: translateX(-50%) translateY(-50%);
+ }
+ `,
+ ],
+})
+export class LoadingComponent implements OnInit {
+ constructor() {}
+
+ ngOnInit() {}
+}
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
new file mode 100644
index 00000000000..d38b3713b75
--- /dev/null
+++ b/npm/ng-packs/packages/theme-shared/src/lib/directives/loading.directive.ts
@@ -0,0 +1,74 @@
+import {
+ Directive,
+ ElementRef,
+ AfterViewInit,
+ ViewContainerRef,
+ ComponentFactoryResolver,
+ Input,
+ Injector,
+ ComponentRef,
+ ComponentFactory,
+ HostBinding,
+ EmbeddedViewRef,
+ Renderer2,
+ OnInit,
+} from '@angular/core';
+import { LoadingComponent } from '../components/loading/loading.component';
+
+@Directive({ selector: '[abpLoading]' })
+export class LoadingDirective implements OnInit {
+ private _loading: boolean;
+
+ @HostBinding('style.position')
+ position = 'relative';
+
+ @Input('abpLoading')
+ get loading(): boolean {
+ return this._loading;
+ }
+
+ set loading(newValue: boolean) {
+ setTimeout(() => {
+ 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;
+ }, 0);
+ }
+
+ @Input('abpLoadingTargetElement')
+ targetElement: HTMLElement;
+
+ componentRef: ComponentRef;
+ rootNode: HTMLDivElement;
+
+ constructor(
+ private elRef: ElementRef,
+ private vcRef: ViewContainerRef,
+ private cdRes: ComponentFactoryResolver,
+ private injector: Injector,
+ private renderer: Renderer2,
+ ) {}
+
+ ngOnInit() {
+ if (!this.targetElement) {
+ const { offsetHeight, offsetWidth } = this.elRef.nativeElement;
+ if (!offsetHeight && !offsetWidth && this.elRef.nativeElement.children.length) {
+ this.targetElement = this.elRef.nativeElement.children[0] as HTMLElement;
+ } else {
+ this.targetElement = this.elRef.nativeElement;
+ }
+ }
+ }
+}