Add AppState Facade

- Add facade with cross-app loading flag observable
- Update AppComponent to use the facade
- Update language facade to set loading when calling api
This commit is contained in:
Clemente Raposo 2020-03-13 09:28:00 +00:00 committed by Dillon-Brown
parent 5d78c4257a
commit 58c1ff4264
4 changed files with 73 additions and 18 deletions

View file

@ -1,4 +1,6 @@
<app-full-page-spinner *ngIf="loading"></app-full-page-spinner>
<ng-container *ngIf="(appState$ | async) as appState">
<app-full-page-spinner *ngIf="appState.loading"></app-full-page-spinner>
</ng-container>
<scrm-navbar-ui></scrm-navbar-ui>
<scrm-message-ui></scrm-message-ui>
<div #mainOutlet></div>

View file

@ -1,5 +1,7 @@
import {Component, ViewChild, ViewContainerRef, OnInit} from '@angular/core';
import {Event, NavigationCancel, NavigationEnd, NavigationError, NavigationStart, Router} from '@angular/router';
import {AppState, AppStateFacade} from "@base/facades/app-state.facade";
import {Observable} from "rxjs";
@Component({
selector: 'app-root',
@ -8,9 +10,9 @@ import {Event, NavigationCancel, NavigationEnd, NavigationError, NavigationStart
export class AppComponent implements OnInit {
@ViewChild('mainOutlet', {read: ViewContainerRef, static: true})
mainOutlet: ViewContainerRef | undefined;
loading = false;
appState$: Observable<AppState> = this.appStateFacade.vm$;
constructor(private router: Router) {
constructor(private router: Router, private appStateFacade: AppStateFacade) {
router.events.subscribe((routerEvent: Event) => this.checkRouterEvent(routerEvent));
}
@ -19,13 +21,13 @@ export class AppComponent implements OnInit {
private checkRouterEvent(routerEvent: Event) {
if (routerEvent instanceof NavigationStart) {
this.loading = true;
this.appStateFacade.updateLoading(true);
}
if (routerEvent instanceof NavigationEnd ||
routerEvent instanceof NavigationCancel ||
routerEvent instanceof NavigationError) {
this.loading = false;
this.appStateFacade.updateLoading(false);
}
}
}

View file

@ -0,0 +1,46 @@
import {Injectable} from '@angular/core';
import {BehaviorSubject, Observable, combineLatest} from 'rxjs';
import {map, distinctUntilChanged} from 'rxjs/operators';
export interface AppState {
loading: boolean;
}
let internalState: AppState = {
loading: false
};
@Injectable({
providedIn: 'root',
})
export class AppStateFacade {
protected store = new BehaviorSubject<AppState>(internalState);
protected state$ = this.store.asObservable();
loading$ = this.state$.pipe(map(state => state.loading), distinctUntilChanged());
/**
* ViewModel that resolves once all the data is ready (or updated)...
*/
vm$: Observable<AppState> = combineLatest([this.loading$]).pipe(
map(([loading]) => ({loading}))
);
constructor() {
this.updateState({...internalState, loading: false});
}
public updateLoading(loading: boolean) {
this.updateState({...internalState, loading});
}
/**
* Update the state
* @param state
*/
protected updateState(state: AppState) {
this.store.next(internalState = state);
}
}

View file

@ -1,8 +1,9 @@
import {Injectable} from '@angular/core';
import {BehaviorSubject, Observable, combineLatest, of} from 'rxjs';
import {map, distinctUntilChanged, switchMap} from 'rxjs/operators';
import {map, distinctUntilChanged, switchMap, tap} from 'rxjs/operators';
import {RecordGQL} from '@services/api/graphql-api/api.record.get';
import {AppStateFacade} from "@base/facades/app-state.facade";
export interface LanguageStringMap {
[key: string]: string;
@ -49,12 +50,12 @@ export class LanguageFacade {
})
);
constructor(private recordGQL: RecordGQL) {
constructor(private recordGQL: RecordGQL, private appStateFacade: AppStateFacade) {
}
// ------- Public Methods ------------------------
public updateLanguage(languageType: string) {
public updateLanguage(languageType: string): void {
this.updateState({..._state, languageType});
}
@ -64,7 +65,7 @@ export class LanguageFacade {
const appStrings$ = combineLatest([this.languageType$]).pipe(
switchMap(([languageType]) => this.fetchAppStrings(languageType))
)
);
appStrings$.subscribe(languageStrings => {
this.updateState({..._state, languageStrings});
@ -75,23 +76,27 @@ export class LanguageFacade {
// ------- Private Methods ------------------------
/** Update internal state cache and emit from store... */
protected updateState(state: LanguageState) {
/**
* Update internal state cache and emit from store...
* @param state
*/
protected updateState(state: LanguageState): void {
this.store.next(_state = state);
}
protected fetchAppStrings(language: string): Observable<{}> {
this.appStateFacade.updateLoading(true);
return this.recordGQL
.fetch(this.resourceName, `/api/app-strings/${language}`, this.fieldsMetadata)
.pipe(map(({data}) => {
let items = {};
let items = {};
if (data.appStrings) {
items = data.appStrings.items;
}
if (data.appStrings) {
items = data.appStrings.items;
}
return items;
}));
return items;
}),
tap(() => this.appStateFacade.updateLoading(false)));
}
}