83628: Dynamic theme fixes

This commit is contained in:
Kristof De Langhe
2021-09-22 17:46:55 +02:00
parent 2d638a738e
commit d9db65685b
2 changed files with 33 additions and 15 deletions

View File

@@ -1,4 +1,4 @@
import { delay, distinctUntilChanged, filter, switchMap, take, withLatestFrom } from 'rxjs/operators'; import { distinctUntilChanged, filter, switchMap, take, withLatestFrom } from 'rxjs/operators';
import { import {
AfterViewInit, AfterViewInit,
ChangeDetectionStrategy, ChangeDetectionStrategy,
@@ -13,9 +13,8 @@ import {
ActivatedRouteSnapshot, ActivatedRouteSnapshot,
NavigationCancel, NavigationCancel,
NavigationEnd, NavigationEnd,
NavigationStart, NavigationStart, ResolveEnd,
Router, Router,
RoutesRecognized
} from '@angular/router'; } from '@angular/router';
import { BehaviorSubject, Observable, of } from 'rxjs'; import { BehaviorSubject, Observable, of } from 'rxjs';
@@ -113,7 +112,6 @@ export class AppComponent implements OnInit, AfterViewInit {
this.themeService.getThemeName$().subscribe((themeName: string) => { this.themeService.getThemeName$().subscribe((themeName: string) => {
if (isPlatformBrowser(this.platformId)) { if (isPlatformBrowser(this.platformId)) {
// the theme css will never download server side, so this should only happen on the browser // the theme css will never download server side, so this should only happen on the browser
this.isThemeLoading$.next(true);
this.isThemeCSSLoading$.next(true); this.isThemeCSSLoading$.next(true);
} }
if (hasValue(themeName)) { if (hasValue(themeName)) {
@@ -186,14 +184,14 @@ export class AppComponent implements OnInit, AfterViewInit {
} }
ngAfterViewInit() { ngAfterViewInit() {
this.router.events.pipe( let resolveEndFound = false;
// This fixes an ExpressionChangedAfterItHasBeenCheckedError from being thrown while loading the component this.router.events.subscribe((event) => {
// More information on this bug-fix: https://blog.angular-university.io/angular-debugging/
delay(0)
).subscribe((event) => {
if (event instanceof NavigationStart) { if (event instanceof NavigationStart) {
resolveEndFound = false;
this.isRouteLoading$.next(true); this.isRouteLoading$.next(true);
} else if (event instanceof RoutesRecognized) { this.isThemeLoading$.next(true);
} else if (event instanceof ResolveEnd) {
resolveEndFound = true;
const activatedRouteSnapShot: ActivatedRouteSnapshot = event.state.root; const activatedRouteSnapShot: ActivatedRouteSnapshot = event.state.root;
this.themeService.updateThemeOnRouteChange$(event.urlAfterRedirects, activatedRouteSnapShot).pipe( this.themeService.updateThemeOnRouteChange$(event.urlAfterRedirects, activatedRouteSnapShot).pipe(
switchMap((changed) => { switchMap((changed) => {
@@ -210,6 +208,9 @@ export class AppComponent implements OnInit, AfterViewInit {
event instanceof NavigationEnd || event instanceof NavigationEnd ||
event instanceof NavigationCancel event instanceof NavigationCancel
) { ) {
if (!resolveEndFound) {
this.isThemeLoading$.next(false);
}
this.isRouteLoading$.next(false); this.isRouteLoading$.next(false);
} }
}); });

View File

@@ -3,9 +3,9 @@ import { Store, createFeatureSelector, createSelector, select, Action } from '@n
import { Observable } from 'rxjs/internal/Observable'; import { Observable } from 'rxjs/internal/Observable';
import { ThemeState } from './theme.reducer'; import { ThemeState } from './theme.reducer';
import { SetThemeAction, ThemeActionTypes } from './theme.actions'; import { SetThemeAction, ThemeActionTypes } from './theme.actions';
import { expand, filter, map, startWith, switchMap, take, toArray } from 'rxjs/operators'; import { expand, filter, map, startWith, switchMap, take, tap, toArray } from 'rxjs/operators';
import { hasValue, isNotEmpty } from '../empty.util'; import { hasValue, isNotEmpty } from '../empty.util';
import { Actions, ofType } from '@ngrx/effects'; import { act, Actions, ofType } from '@ngrx/effects';
import { ResolvedAction, ResolverActionTypes } from '../../core/resolving/resolver.actions'; import { ResolvedAction, ResolverActionTypes } from '../../core/resolving/resolver.actions';
import { RemoteData } from '../../core/data/remote-data'; import { RemoteData } from '../../core/data/remote-data';
import { DSpaceObject } from '../../core/shared/dspace-object.model'; import { DSpaceObject } from '../../core/shared/dspace-object.model';
@@ -86,9 +86,10 @@ export class ThemeService {
const action$ = currentTheme$.pipe( const action$ = currentTheme$.pipe(
switchMap((currentTheme: string) => { switchMap((currentTheme: string) => {
const snapshotWithData = this.findRouteData(activatedRouteSnapshot);
if (this.hasDynamicTheme === true && isNotEmpty(this.themes)) { if (this.hasDynamicTheme === true && isNotEmpty(this.themes)) {
if (hasValue(activatedRouteSnapshot) && hasValue(activatedRouteSnapshot.data) && hasValue(activatedRouteSnapshot.data.dso)) { if (hasValue(snapshotWithData) && hasValue(snapshotWithData.data) && hasValue(snapshotWithData.data.dso)) {
const dsoRD: RemoteData<DSpaceObject> = activatedRouteSnapshot.data.dso; const dsoRD: RemoteData<DSpaceObject> = snapshotWithData.data.dso;
if (dsoRD.hasSucceeded) { if (dsoRD.hasSucceeded) {
// Start with the resolved dso and go recursively through its parents until you reach the top-level community // Start with the resolved dso and go recursively through its parents until you reach the top-level community
return observableOf(dsoRD.payload).pipe( return observableOf(dsoRD.payload).pipe(
@@ -123,10 +124,10 @@ export class ThemeService {
// If there are no themes configured, do nothing // If there are no themes configured, do nothing
return [new NoOpAction()]; return [new NoOpAction()];
}), }),
take(1),
); );
action$.pipe( action$.pipe(
take(1),
filter((action) => action.type !== NO_OP_ACTION_TYPE), filter((action) => action.type !== NO_OP_ACTION_TYPE),
).subscribe((action) => { ).subscribe((action) => {
this.store.dispatch(action); this.store.dispatch(action);
@@ -137,6 +138,22 @@ export class ThemeService {
); );
} }
findRouteData(...routes: ActivatedRouteSnapshot[]) {
const result = routes.find((route) => hasValue(route.data.dso));
if (hasValue(result)) {
return result;
} else {
const nextLevelRoutes = routes
.map((route: ActivatedRouteSnapshot) => route.children)
.reduce((combined: ActivatedRouteSnapshot[], current: ActivatedRouteSnapshot[]) => [...combined, ...current]);
if (isNotEmpty(nextLevelRoutes)) {
return this.findRouteData(...nextLevelRoutes);
} else {
return undefined;
}
}
}
/** /**
* An rxjs operator that will return an array of all the ancestors of the DSpaceObject used as * An rxjs operator that will return an array of all the ancestors of the DSpaceObject used as
* input. The initial DSpaceObject will be the first element of the output array, followed by * input. The initial DSpaceObject will be the first element of the output array, followed by