mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-16 14:33:03 +00:00
Add support for dynamic themes
This commit is contained in:
116
src/app/shared/theme-support/themed.component.ts
Normal file
116
src/app/shared/theme-support/themed.component.ts
Normal file
@@ -0,0 +1,116 @@
|
||||
import {
|
||||
Component,
|
||||
ViewChild,
|
||||
ViewContainerRef,
|
||||
ComponentRef,
|
||||
SimpleChanges,
|
||||
OnInit,
|
||||
OnDestroy,
|
||||
ComponentFactoryResolver,
|
||||
ChangeDetectorRef,
|
||||
OnChanges
|
||||
} from '@angular/core';
|
||||
import { hasValue, isNotEmpty } from '../empty.util';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { ThemeService } from './theme.service';
|
||||
import { fromPromise } from 'rxjs/internal-compatibility';
|
||||
import { catchError, switchMap, map } from 'rxjs/operators';
|
||||
import { GenericConstructor } from '../../core/shared/generic-constructor';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-themed',
|
||||
styleUrls: ['./themed.component.scss'],
|
||||
templateUrl: './themed.component.html',
|
||||
})
|
||||
export abstract class ThemedComponent<T> implements OnInit, OnDestroy, OnChanges {
|
||||
@ViewChild('vcr', { read: ViewContainerRef }) vcr: ViewContainerRef;
|
||||
protected compRef: ComponentRef<T>;
|
||||
|
||||
protected lazyLoadSub: Subscription;
|
||||
protected themeSub: Subscription;
|
||||
|
||||
protected inAndOutputNames: (keyof T & keyof this)[] = [];
|
||||
|
||||
constructor(
|
||||
protected resolver: ComponentFactoryResolver,
|
||||
protected cdr: ChangeDetectorRef,
|
||||
protected themeService: ThemeService
|
||||
) {
|
||||
}
|
||||
|
||||
protected abstract getComponentName(): string;
|
||||
|
||||
protected abstract importThemedComponent(themeName: string): Promise<any>;
|
||||
protected abstract importUnthemedComponent(): Promise<any>;
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
// if an input or output has changed
|
||||
if (this.inAndOutputNames.some((name: any) => hasValue(changes[name]))) {
|
||||
this.connectInputsAndOutputs();
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.destroyComponentInstance();
|
||||
this.themeSub = this.themeService.getThemeName$().subscribe(() => {
|
||||
this.renderComponentInstance();
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
[this.themeSub, this.lazyLoadSub].filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe());
|
||||
this.destroyComponentInstance();
|
||||
}
|
||||
|
||||
protected renderComponentInstance(): void {
|
||||
this.destroyComponentInstance();
|
||||
|
||||
if (hasValue(this.lazyLoadSub)) {
|
||||
this.lazyLoadSub.unsubscribe();
|
||||
}
|
||||
|
||||
this.lazyLoadSub =
|
||||
fromPromise(this.importThemedComponent(this.themeService.getThemeName())).pipe(
|
||||
// if there is no themed version of the component an exception is thrown,
|
||||
// catch it and return null instead
|
||||
catchError(() => [null]),
|
||||
switchMap((themedFile: any) => {
|
||||
if (hasValue(themedFile) && hasValue(themedFile[this.getComponentName()])) {
|
||||
// if the file is not null, and exports a component with the specified name,
|
||||
// return that component
|
||||
return [themedFile[this.getComponentName()]];
|
||||
} else {
|
||||
// otherwise import and return the default component
|
||||
return fromPromise(this.importUnthemedComponent()).pipe(
|
||||
map((unthemedFile: any) => {
|
||||
return unthemedFile[this.getComponentName()];
|
||||
})
|
||||
);
|
||||
}
|
||||
}),
|
||||
).subscribe((constructor: GenericConstructor<T>) => {
|
||||
const factory = this.resolver.resolveComponentFactory(constructor);
|
||||
this.compRef = this.vcr.createComponent(factory);
|
||||
this.connectInputsAndOutputs();
|
||||
this.cdr.markForCheck();
|
||||
});
|
||||
}
|
||||
|
||||
protected destroyComponentInstance(): void {
|
||||
if (hasValue(this.compRef)) {
|
||||
this.compRef.destroy();
|
||||
this.compRef = null;
|
||||
}
|
||||
if (hasValue(this.vcr)) {
|
||||
this.vcr.clear();
|
||||
}
|
||||
}
|
||||
|
||||
protected connectInputsAndOutputs(): void {
|
||||
if (isNotEmpty(this.inAndOutputNames) && hasValue(this.compRef) && hasValue(this.compRef.instance)) {
|
||||
this.inAndOutputNames.forEach((name: any) => {
|
||||
this.compRef.instance[name] = this[name];
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user