Я сделал модуль темы для своего angular приложения, которое позволяет пользователю переключаться между светлым и темным режимами. Он использует службу, директиву и отдельный модуль и отлично работает, кроме случаев, когда вы пытаетесь использовать прямой вход в <app-root></app-root>
, и это необходимо, потому что в противном случае тема не применяется ко всему DOM.
theme.service. ts:
import { Injectable, Inject, EventEmitter } from '@angular/core';
import { THEMES, ACTIVE_THEME, Theme } from './themes/symbols';
@Injectable()
export class ThemeService {
themeChange = new EventEmitter<Theme>();
constructor(
@Inject(THEMES) public themes: Theme[],
@Inject(ACTIVE_THEME) public theme: string
) {
}
getTheme(name: string) {
const theme = this.themes.find(t => t.name === name);
if (!theme) {
throw new Error(`Theme not found: '${name}'`);
}
return theme;
}
getActiveTheme() {
return this.getTheme(this.theme);
}
getProperty(propName: string) {
return this.getActiveTheme().properties[propName];
}
setTheme(name: string) {
this.theme = name;
this.themeChange.emit( this.getActiveTheme());
}
registerTheme(theme: Theme) {
this.themes.push(theme);
}
updateTheme(name: string, properties: { [key: string]: string; }) {
const theme = this.getTheme(name);
theme.properties = {
...theme.properties,
...properties
};
if (name === this.theme) {
this.themeChange.emit(theme);
}
}
}
theme.directive.ts
import { Directive, OnInit, OnDestroy, ElementRef } from '@angular/core';
import { ThemeService } from './theme.service';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { Theme } from './themes/symbols';
@Directive({
selector: '[theme]'
})
export class ThemeDirective implements OnInit, OnDestroy {
private _destroy$ = new Subject();
constructor(
private _elementRef: ElementRef,
private _themeService: ThemeService
) {}
ngOnInit() {
const active = this._themeService.getActiveTheme();
if (active) {
this.updateTheme(active);
}
this._themeService.themeChange
.pipe(takeUntil(this._destroy$))
.subscribe((theme: Theme) => this.updateTheme(theme));
}
ngOnDestroy() {
this._destroy$.next();
this._destroy$.complete();
}
updateTheme(theme: Theme) {
// project properties onto the element
for (const key in theme.properties) {
this._elementRef.nativeElement.style.setProperty(key, theme.properties[key]);
}
// remove old theme
for (const name of this._themeService.theme) {
this._elementRef.nativeElement.classList.remove(`${name}-theme`);
}
// alias element with theme name
this._elementRef.nativeElement.classList.add(`${theme.name}-theme`);
}
}
theme.module.ts
import { NgModule, ModuleWithProviders, InjectionToken } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ThemeService } from './theme.service';
import { ThemeDirective } from './theme.directive';
import { THEMES, ACTIVE_THEME, ThemeOptions } from './themes/symbols';
@NgModule({
imports: [CommonModule],
providers: [ThemeService],
declarations: [ThemeDirective],
exports: [ThemeDirective]
})
export class ThemeModule {
static forRoot(options: ThemeOptions): ModuleWithProviders<ThemeModule> {
return {
ngModule: ThemeModule,
providers: [
{
provide: THEMES,
useValue: options.themes
},
{
provide: ACTIVE_THEME,
useValue: options.active
}
]
};
}
}
Затем я объявляю в импорте app.module:
ThemeModule.forRoot({
themes: [lightTheme, darkTheme],
active: 'light'
})
Теперь я могу использовать эту директиву в app.component. html:
<body theme>
<app-nav-menu (toggleTheme)="toggleTheme()"></app-nav-menu>
<div class="container parent-container">
<router-outlet></router-outlet>
</div>
</body>
, однако я не могу использовать ее в index. html, так как он хотел бы применить эту директиву к тегу app- root или самому тегу html, чтобы тема влияла на всю DOM.
Есть идеи, как достичь моей цели? любая помощь приветствуется.