diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts
index 0297262..3548250 100644
--- a/src/app/app-routing.module.ts
+++ b/src/app/app-routing.module.ts
@@ -1,10 +1,20 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
+import { LoaderTestComponent } from './loader-test/loader-test.component';
-const routes: Routes = [];
+const routes: Routes = [
+ {
+ path: 'feat/loader-interceptor-service',
+ component: LoaderTestComponent,
+ },
+ {
+ path: '**',
+ redirectTo: 'feat/loader-interceptor-service',
+ },
+];
@NgModule({
imports: [RouterModule.forRoot(routes)],
- exports: [RouterModule]
+ exports: [RouterModule],
})
-export class AppRoutingModule { }
+export class AppRoutingModule {}
diff --git a/src/app/app.component.ts b/src/app/app.component.ts
index 57f36a6..8a0861a 100644
--- a/src/app/app.component.ts
+++ b/src/app/app.component.ts
@@ -2,7 +2,8 @@ import { Component } from '@angular/core';
@Component({
selector: 'app-root',
- templateUrl: './app.component.html',
+ // templateUrl: './app.component.html',
+ template: ``,
styleUrls: ['./app.component.scss']
})
export class AppComponent {
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index b1c6c96..ee1e003 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -1,18 +1,23 @@
+import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
+import { DedicatedLoaderDirective } from './core/directives';
+import { HttpLoaderInterceptor } from './core/interceptors';
+import { LoaderTestComponent } from './loader-test/loader-test.component';
@NgModule({
- declarations: [
- AppComponent
+ declarations: [AppComponent, LoaderTestComponent, DedicatedLoaderDirective],
+ imports: [BrowserModule, AppRoutingModule, HttpClientModule],
+ providers: [
+ {
+ provide: HTTP_INTERCEPTORS,
+ useClass: HttpLoaderInterceptor,
+ multi: true,
+ },
],
- imports: [
- BrowserModule,
- AppRoutingModule
- ],
- providers: [],
- bootstrap: [AppComponent]
+ bootstrap: [AppComponent],
})
-export class AppModule { }
+export class AppModule {}
diff --git a/src/app/core/directives/dedicated-loader.directive.spec.ts b/src/app/core/directives/dedicated-loader.directive.spec.ts
new file mode 100644
index 0000000..d7203b8
--- /dev/null
+++ b/src/app/core/directives/dedicated-loader.directive.spec.ts
@@ -0,0 +1,8 @@
+import { DedicatedLoaderDirective } from './dedicated-loader.directive';
+
+describe('DedicatedLoaderDirective', () => {
+ it('should create an instance', () => {
+ const directive = new DedicatedLoaderDirective();
+ expect(directive).toBeTruthy();
+ });
+});
diff --git a/src/app/core/directives/dedicated-loader.directive.ts b/src/app/core/directives/dedicated-loader.directive.ts
new file mode 100644
index 0000000..627fd89
--- /dev/null
+++ b/src/app/core/directives/dedicated-loader.directive.ts
@@ -0,0 +1,20 @@
+import { Directive, ElementRef, Input, Renderer2 } from '@angular/core';
+
+@Directive({
+ selector: '[appDedicatedLoader]',
+})
+export class DedicatedLoaderDirective {
+ private readonly loaderClass = 'directive-loader'; // defined in styles.scss
+
+ @Input() set loaderStatus(value: boolean) {
+ if (this.el.nativeElement) {
+ if (value) {
+ this.el.nativeElement.classList.add(this.loaderClass);
+ } else {
+ this.el.nativeElement.classList.remove(this.loaderClass);
+ }
+ }
+ }
+
+ constructor(private el: ElementRef) {}
+}
diff --git a/src/app/core/directives/index.ts b/src/app/core/directives/index.ts
new file mode 100644
index 0000000..fdeba89
--- /dev/null
+++ b/src/app/core/directives/index.ts
@@ -0,0 +1 @@
+export * from './dedicated-loader.directive';
diff --git a/src/app/core/interceptors/http-loader.interceptor.spec.ts b/src/app/core/interceptors/http-loader.interceptor.spec.ts
new file mode 100644
index 0000000..34616c7
--- /dev/null
+++ b/src/app/core/interceptors/http-loader.interceptor.spec.ts
@@ -0,0 +1,16 @@
+import { TestBed } from '@angular/core/testing';
+
+import { HttpLoaderInterceptor } from './http-loader.interceptor';
+
+describe('HttLoaderInterceptor', () => {
+ beforeEach(() => TestBed.configureTestingModule({
+ providers: [
+ HttpLoaderInterceptor
+ ]
+ }));
+
+ it('should be created', () => {
+ const interceptor: HttpLoaderInterceptor = TestBed.inject(HttpLoaderInterceptor);
+ expect(interceptor).toBeTruthy();
+ });
+});
diff --git a/src/app/core/interceptors/http-loader.interceptor.ts b/src/app/core/interceptors/http-loader.interceptor.ts
new file mode 100644
index 0000000..4baf0a5
--- /dev/null
+++ b/src/app/core/interceptors/http-loader.interceptor.ts
@@ -0,0 +1,101 @@
+import { Injectable } from '@angular/core';
+import {
+ HttpRequest,
+ HttpHandler,
+ HttpEvent,
+ HttpInterceptor,
+} from '@angular/common/http';
+import { Observable } from 'rxjs';
+import { HeaderName } from 'src/app/shared/enums';
+import { finalize } from 'rxjs/operators';
+import { LoaderStateService } from '../services/loader-state.service';
+
+@Injectable()
+export class HttpLoaderInterceptor implements HttpInterceptor {
+ /**
+ * Array of requests that are ignored by default, e.g. login, logout, etc...
+ */
+ private readonly requestForIgnore = ['v1/login', 'v2/logut'];
+
+ constructor(private loaderStateService: LoaderStateService) {}
+
+ intercept(
+ request: HttpRequest,
+ next: HttpHandler
+ ): Observable> {
+ const req =
+ this.handleIgnoredRequest(request, next) ||
+ this.handleUserIgnoredRequest(request, next) ||
+ this.handleDedicatedRequest(request, next) ||
+ this.handleRequestWithLoader(request, next);
+
+ return req;
+ }
+
+ private handleUserIgnoredRequest(
+ request: HttpRequest,
+ next: HttpHandler
+ ): Observable> | null {
+ let output = null;
+
+ if (request.headers.get(HeaderName.userIgnoredLoader)) {
+ const req = request.clone({
+ headers: request.headers.delete(HeaderName.userIgnoredLoader),
+ });
+
+ output = next.handle(req);
+ }
+
+ return output;
+ }
+
+ private handleDedicatedRequest(
+ request: HttpRequest,
+ next: HttpHandler
+ ): Observable> | null {
+ let output = null;
+ const key = request.headers.get(HeaderName.showDedicatedLoader);
+
+ if (key) {
+ const req = request.clone({
+ headers: request.headers.delete(HeaderName.showDedicatedLoader),
+ });
+
+ this.loaderStateService.showDedicatedFor(key);
+ output = next
+ .handle(req)
+ .pipe(finalize(() => this.loaderStateService.hideDedicatedFor(key)));
+ }
+
+ return output;
+ }
+
+ private handleIgnoredRequest(
+ request: HttpRequest,
+ next: HttpHandler
+ ): Observable> | null {
+ let output = next.handle(request);
+ const url = request.url.toLowerCase();
+
+ const requestIgnored = !this.requestForIgnore.filter((ignoreReq) =>
+ url.toLowerCase().endsWith(ignoreReq)
+ ).length;
+
+ if (requestIgnored) {
+ output = null;
+ }
+
+ return output;
+ }
+
+ private handleRequestWithLoader(
+ request: HttpRequest,
+ next: HttpHandler
+ ): Observable> {
+ const uniqueKey = this.loaderStateService.showMain();
+
+ return next
+ .handle(request)
+ .pipe(finalize(() => this.loaderStateService.hideMain(uniqueKey)));
+ }
+}
diff --git a/src/app/core/interceptors/index.ts b/src/app/core/interceptors/index.ts
new file mode 100644
index 0000000..86981ce
--- /dev/null
+++ b/src/app/core/interceptors/index.ts
@@ -0,0 +1 @@
+export * from './http-loader.interceptor';
diff --git a/src/app/core/services/index.ts b/src/app/core/services/index.ts
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/core/services/loader-state.service.spec.ts b/src/app/core/services/loader-state.service.spec.ts
new file mode 100644
index 0000000..7245ea1
--- /dev/null
+++ b/src/app/core/services/loader-state.service.spec.ts
@@ -0,0 +1,16 @@
+import { TestBed } from '@angular/core/testing';
+
+import { LoaderStateService } from './loader-state.service';
+
+describe('LoaderStateServiceService', () => {
+ let service: LoaderStateService;
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({});
+ service = TestBed.inject(LoaderStateService);
+ });
+
+ it('should be created', () => {
+ expect(service).toBeTruthy();
+ });
+});
diff --git a/src/app/core/services/loader-state.service.ts b/src/app/core/services/loader-state.service.ts
new file mode 100644
index 0000000..349b13c
--- /dev/null
+++ b/src/app/core/services/loader-state.service.ts
@@ -0,0 +1,149 @@
+import { Injectable } from '@angular/core';
+import { BehaviorSubject, Observable } from 'rxjs';
+import { map, skip } from 'rxjs/operators';
+import { HeaderName } from 'src/app/shared/enums';
+import { LoaderHeader } from 'src/app/shared/models';
+
+type LoaderKeyTimeoutState = { [uniqueKey: string]: number };
+
+// todo:
+// - navigation & tab change
+// - ngb-nav
+
+@Injectable({
+ providedIn: 'root',
+})
+export class LoaderStateService {
+ //#region Class properties
+
+ private readonly maxRequestLength = 61_000; // [ms];
+
+ private mainLoaderState$ = new BehaviorSubject({});
+ private dedicatedLoaderState$ = new BehaviorSubject(
+ {}
+ );
+
+ //#endregion
+
+ //#region Getters for main/dedicated loader
+
+ public get showMainLoader$(): Observable {
+ return this.mainLoaderState$.asObservable().pipe(
+ skip(1), // we're not interested in the default state
+ map(
+ (currentState: LoaderKeyTimeoutState) =>
+ Object.keys(currentState).length !== 0
+ )
+ );
+ }
+
+ public get dedicatedLoaders$(): Observable {
+ return this.dedicatedLoaderState$.asObservable();
+ }
+
+ public dedicatedLoaderFor(key: string): Observable {
+ return this.dedicatedLoaders$.pipe(
+ map((state: LoaderKeyTimeoutState) => key in state)
+ );
+ }
+
+ //#endregion
+
+ //#region Toggling methods
+
+ public showMain(key?: string): string {
+ if (!key) {
+ key = this.generateUniqueKey();
+ }
+ this.modifyStateViaAdd(this.mainLoaderState$, key, 'main');
+
+ return key;
+ }
+
+ public hideMain(key: string): void {
+ this.modifyStateViaRmv(this.mainLoaderState$, key);
+ }
+
+ public showDedicatedFor(key: string): void {
+ this.modifyStateViaAdd(this.dedicatedLoaderState$, key, 'dedicated');
+ }
+
+ public hideDedicatedFor(key: string): void {
+ this.modifyStateViaRmv(this.dedicatedLoaderState$, key);
+ }
+
+ //#endregion
+
+ //#region Header utility methods
+
+ public generateHeaderFor(names: HeaderName[]): LoaderHeader {
+ const output: LoaderHeader = {};
+
+ const nonDefaultValues = new Map([
+ [HeaderName.showDedicatedLoader, this.generateUniqueKey()],
+ ]);
+
+ for (let headerName of names) {
+ output[headerName] = nonDefaultValues.get(headerName) ?? 'present';
+ }
+
+ return output;
+ }
+
+ public initLoaderForDedicatedHeader(): [LoaderHeader, Observable] {
+ const header = this.generateHeaderFor([HeaderName.showDedicatedLoader]);
+
+ return [
+ header,
+ this.dedicatedLoaderFor(header[HeaderName.showDedicatedLoader]),
+ ];
+ }
+
+ //#endregion
+
+ //#region State utility methods
+
+ private modifyStateViaAdd(
+ state$: BehaviorSubject,
+ key: string,
+ state: 'main' | 'dedicated' = 'main'
+ ): LoaderKeyTimeoutState {
+ const current = state$.getValue();
+
+ current[key] = window.setTimeout(
+ () =>
+ state === 'main' ? this.hideMain(key) : this.hideDedicatedFor(key),
+ this.maxRequestLength
+ );
+
+ state$.next(current);
+
+ return current;
+ }
+
+ private modifyStateViaRmv(
+ state$: BehaviorSubject,
+ key: string
+ ): LoaderKeyTimeoutState {
+ const current = state$.getValue();
+
+ if (current[key]) {
+ window.clearTimeout(current[key]);
+ delete current[key];
+
+ state$.next(current);
+ }
+
+ return current;
+ }
+
+ //#endregion
+
+ //#region Utility methods
+
+ public generateUniqueKey(): string {
+ return `present-and-unique-key-${Date.now() + Math.random()}`;
+ }
+
+ //#endregion
+}
diff --git a/src/app/loader-test/loader-test.component.html b/src/app/loader-test/loader-test.component.html
new file mode 100644
index 0000000..2231573
--- /dev/null
+++ b/src/app/loader-test/loader-test.component.html
@@ -0,0 +1,53 @@
+loader-test works!
+
+
+
+
main state
+
{{ mainState$ | async | json }}
+
+
+
dedicated state:
+
{{ dedicatedState$ | async | json }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/app/loader-test/loader-test.component.scss b/src/app/loader-test/loader-test.component.scss
new file mode 100644
index 0000000..d89087f
--- /dev/null
+++ b/src/app/loader-test/loader-test.component.scss
@@ -0,0 +1,19 @@
+#main-loader {
+ display: block;
+ width: 100vw;
+ height: 50px;
+ background-color: darkcyan;
+ position: absolute;
+ bottom: 0px;
+ left: 0px;
+}
+
+#dedicated-loader {
+ display: block;
+ width: 100vw;
+ height: 25px;
+ background-color: chocolate;
+ position: absolute;
+ bottom: 50px;
+ left: 0px;
+}
diff --git a/src/app/loader-test/loader-test.component.spec.ts b/src/app/loader-test/loader-test.component.spec.ts
new file mode 100644
index 0000000..572026e
--- /dev/null
+++ b/src/app/loader-test/loader-test.component.spec.ts
@@ -0,0 +1,25 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { LoaderTestComponent } from './loader-test.component';
+
+describe('LoaderTestComponent', () => {
+ let component: LoaderTestComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ declarations: [ LoaderTestComponent ]
+ })
+ .compileComponents();
+ });
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(LoaderTestComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/loader-test/loader-test.component.ts b/src/app/loader-test/loader-test.component.ts
new file mode 100644
index 0000000..5e47981
--- /dev/null
+++ b/src/app/loader-test/loader-test.component.ts
@@ -0,0 +1,238 @@
+import { HttpClient } from '@angular/common/http';
+import {
+ ChangeDetectionStrategy,
+ ChangeDetectorRef,
+ Component,
+ OnInit,
+} from '@angular/core';
+import { Observable } from 'rxjs';
+import { LoaderStateService } from '../core/services/loader-state.service';
+import { HeaderName } from '../shared/enums';
+import { LoaderHeader } from '../shared/models';
+
+@Component({
+ selector: 'app-loader-test',
+ templateUrl: './loader-test.component.html',
+ styleUrls: ['./loader-test.component.scss'],
+ changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class LoaderTestComponent implements OnInit {
+ //#region Don't do this at home
+ public mainState$ = this.loaderStateService['mainLoaderState$'];
+ public dedicatedState$ = this.loaderStateService['dedicatedLoaderState$'];
+ //#endregion
+
+ public loader$ = this.loaderStateService.showMainLoader$;
+ public dedicatedLoader$: Observable;
+
+ private dedicatedHeader: LoaderHeader;
+
+ constructor(
+ private httpClient: HttpClient,
+ private loaderStateService: LoaderStateService,
+ private cdr: ChangeDetectorRef
+ ) {}
+
+ ngOnInit(): void {
+ // just test loaders
+ // setTimeout(() => this.initFakeLoaders(), 1_500);
+
+ // test main loader
+ // setTimeout(() => this.initMainLoader(), 3_000);
+
+ // test dedicated loader
+ // setTimeout(
+ // () => this.initDedicatedHeaderLoader().initDedicatedLoader(),
+ // 3_500
+ // );
+
+ // test w/o loader
+ // setTimeout(() => this.initRequestWithoutLoader(), 4_500);
+ }
+
+ //#region Init methods
+
+ private initFakeLoaders(): this {
+ const dedicatedLoaderKey = 'lorem-ipsum';
+ const mainLoaderKey = 'aaaaaa';
+
+ this.loaderStateService.showMain(mainLoaderKey);
+ this.loaderStateService.showDedicatedFor(dedicatedLoaderKey);
+ this.dedicatedLoader$ =
+ this.loaderStateService.dedicatedLoaderFor(dedicatedLoaderKey);
+
+ setTimeout(() => {
+ this.loaderStateService.hideMain(mainLoaderKey);
+ }, 500);
+ setTimeout(
+ () => this.loaderStateService.hideDedicatedFor(dedicatedLoaderKey),
+ 700
+ );
+
+ return this;
+ }
+
+ private initRequestWithoutLoader(): this {
+ this.dedicatedHeader = this.loaderStateService.generateHeaderFor([
+ HeaderName.userIgnoredLoader,
+ ]);
+
+ this.httpClient
+ .get('http://dev.qposoft.com:4082/api/users', {
+ headers: this.dedicatedHeader,
+ })
+ .subscribe(console.log);
+
+ return this;
+ }
+
+ private initDedicatedLoader(): this {
+ const uniqueId = this.loaderStateService.generateUniqueKey();
+ this.dedicatedLoader$ =
+ this.loaderStateService.dedicatedLoaderFor(uniqueId);
+
+ this.httpClient
+ // .get('http://dev.qposoft.com:4082/api/users', {
+ .get('http://dev.qposoft.com:4082/api/sleep/3', {
+ headers: {
+ [HeaderName.showDedicatedLoader]: uniqueId,
+ },
+ })
+ .subscribe(console.log);
+
+ return this;
+ }
+
+ private initDedicatedHeaderLoader(): this {
+ return this;
+ [this.dedicatedHeader, this.dedicatedLoader$] =
+ this.loaderStateService.initLoaderForDedicatedHeader();
+
+ /**
+ * HEADS UP:
+ * Because of the "changeDetection: ChangeDetectionStrategy.OnPush"
+ * inside component decorator, above line is updated property that
+ * is used inside tpl, and we need to manually trigger update.
+ * When we remove OnPush this isn't the case.
+ * But, IMHO there is no need to worry about this code, because,
+ * as Nebojsa suggested, we should create directive for dedicated loader,
+ * so this piece of code will be part only of that directive, and it should
+ * receive as @input value for showing/hiding dedicated loader.
+ */
+ this.cdr.detectChanges();
+
+ return this;
+ }
+
+ private initMainLoader(): this {
+ this.httpClient
+ .get('http://dev.qposoft.com:4082/api/sleep/5')
+ .subscribe(console.log);
+
+ return this;
+ }
+
+ //#endregion
+
+ //#region UI events
+
+ public async onTestMainLoader(): Promise {
+ this.initMainLoader();
+ }
+
+ //#endregion
+
+ //#region Ad-hock dedicated loaders testing
+
+ public dedicatedLoader1$: Observable;
+ public dedicatedLoader2$: Observable;
+ public async onTestDedicatedLoader(id: string): Promise {
+ const uniqueId = this.loaderStateService.generateUniqueKey() + id;
+ if (id === '1') {
+ this.dedicatedLoader1$ =
+ this.loaderStateService.dedicatedLoaderFor(uniqueId);
+ } else {
+ this.dedicatedLoader2$ =
+ this.loaderStateService.dedicatedLoaderFor(uniqueId);
+ }
+
+ this.httpClient
+ // .get('http://dev.qposoft.com:4082/api/users', {
+ .get('http://dev.qposoft.com:4082/api/sleep/3', {
+ headers: {
+ [HeaderName.showDedicatedLoader]: uniqueId,
+ },
+ })
+ .subscribe(console.log);
+ }
+
+ //#endregion
+
+ //#region Proposals on how to use dedicated loader
+
+ public dedicatedLoaderVisible$: Observable;
+
+ public async onDedicatedLoaderEventProposal1(): Promise {
+ // prepare unique id
+ const uniqueId = this.loaderStateService.generateUniqueKey();
+ // prepare loader visibility Observable
+ this.dedicatedLoaderVisible$ =
+ this.loaderStateService.dedicatedLoaderFor(uniqueId);
+ // make call
+ this.httpClient
+ .get('http://dev.qposoft.com:4082/api/sleep/3', {
+ headers: {
+ [HeaderName.showDedicatedLoader]: uniqueId,
+ },
+ })
+ .subscribe(console.log);
+ }
+
+ public async onDedicatedLoaderEventProposal2(): Promise {
+ // prepare unique id
+ const uniqueId = `some-random-and-unique-id-${Date.now()}`;
+ // prepare loader visibility Observable
+ this.dedicatedLoaderVisible$ =
+ this.loaderStateService.dedicatedLoaderFor(uniqueId);
+ // make call
+ this.httpClient
+ .get('http://dev.qposoft.com:4082/api/sleep/3', {
+ headers: {
+ [HeaderName.showDedicatedLoader]: uniqueId,
+ },
+ })
+ .subscribe(console.log);
+ }
+
+ public async onDedicatedLoaderEventProposal3(): Promise {
+ // prepare header and unique id
+ const headers = this.loaderStateService.generateHeaderFor([
+ HeaderName.showDedicatedLoader,
+ ]);
+ // prepare visibility Observable
+ this.dedicatedLoaderVisible$ = this.loaderStateService.dedicatedLoaderFor(
+ headers[HeaderName.showDedicatedLoader]
+ );
+ // make call
+ this.httpClient
+ .get('http://dev.qposoft.com:4082/api/sleep/3', {
+ headers,
+ })
+ .subscribe(console.log);
+ }
+
+ public async onDedicatedLoaderEventProposal4(): Promise {
+ // prepare header, unique id and visibility Observable
+ let headers = {};
+ [headers, this.dedicatedLoaderVisible$] =
+ this.loaderStateService.initLoaderForDedicatedHeader();
+ // make call
+ this.httpClient
+ .get('http://dev.qposoft.com:4082/api/sleep/3', {
+ headers,
+ })
+ .subscribe(console.log);
+ }
+
+ //#endregion
+}
diff --git a/src/app/shared/enums/header-name.enum.ts b/src/app/shared/enums/header-name.enum.ts
new file mode 100644
index 0000000..a52de38
--- /dev/null
+++ b/src/app/shared/enums/header-name.enum.ts
@@ -0,0 +1,4 @@
+export enum HeaderName {
+ userIgnoredLoader = 'PROJECT-User-Ignored-Loader',
+ showDedicatedLoader = 'PROJECT-Show-Dedicated-Loader',
+}
diff --git a/src/app/shared/enums/index.ts b/src/app/shared/enums/index.ts
new file mode 100644
index 0000000..2586c51
--- /dev/null
+++ b/src/app/shared/enums/index.ts
@@ -0,0 +1 @@
+export * from './header-name.enum';
diff --git a/src/app/shared/models/index.ts b/src/app/shared/models/index.ts
new file mode 100644
index 0000000..9fba242
--- /dev/null
+++ b/src/app/shared/models/index.ts
@@ -0,0 +1 @@
+export * from './loader-header.model';
diff --git a/src/app/shared/models/loader-header.model.ts b/src/app/shared/models/loader-header.model.ts
new file mode 100644
index 0000000..905e9f2
--- /dev/null
+++ b/src/app/shared/models/loader-header.model.ts
@@ -0,0 +1,3 @@
+import { HeaderName } from '../enums/header-name.enum';
+
+export type LoaderHeader = { [key in HeaderName]?: string };
diff --git a/src/styles.scss b/src/styles.scss
index 90d4ee0..76e3a2c 100644
--- a/src/styles.scss
+++ b/src/styles.scss
@@ -1 +1,5 @@
/* You can add global styles to this file, and also import other style files */
+.directive-loader {
+ background-color: chocolate;
+ color: white;
+}