import { Injectable, RendererFactory2, Renderer2, Inject, EventEmitter } from '@angular/core'; import { Observable, BehaviorSubject, combineLatest } from 'rxjs'; import { DOCUMENT } from '@angular/common'; @Injectable({ providedIn: 'root' }) export class ThemeService { private _mainTheme$: BehaviorSubject = new BehaviorSubject('theme-default'); private _darkMode$: BehaviorSubject = new BehaviorSubject(false); private _renderer: Renderer2; private head: HTMLElement; private themeLinks: HTMLElement[] = []; darkMode$: Observable = this._darkMode$.asObservable(); theme$: Observable<[string, boolean]>; public themeChanged = new EventEmitter(); public savedTheme: string = 'dark'; constructor( rendererFactory: RendererFactory2, @Inject(DOCUMENT) document: Document ) { this.head = document.head; this._renderer = rendererFactory.createRenderer(null, null); this.theme$ = combineLatest(this._mainTheme$, this._darkMode$); this.theme$.subscribe(async ([mainTheme, darkMode]) => { const cssExt = '.css'; const cssFilename = darkMode ? mainTheme + '-dark' + cssExt : mainTheme + cssExt; await this.loadCss(cssFilename); if (this.themeLinks.length == 2) this._renderer.removeChild(this.head, this.themeLinks.shift()); }) } getActualTheme() { return this.savedTheme; } setMainTheme(name: string) { this._mainTheme$.next(name); } setDarkMode(value: boolean) { this._darkMode$.next(value); localStorage.removeItem('theme'); if (value) { this.savedTheme = 'dark'; this.themeChanged.emit(this.savedTheme); localStorage.setItem('theme', 'dark'); } else { this.savedTheme = 'light'; this.themeChanged.emit(this.savedTheme); localStorage.setItem('theme', 'light'); } } private async loadCss(filename: string) { return new Promise(resolve => { const linkEl: HTMLElement = this._renderer.createElement('link'); this._renderer.setAttribute(linkEl, 'rel', 'stylesheet'); this._renderer.setAttribute(linkEl, 'type', 'text/css'); this._renderer.setAttribute(linkEl, 'href', filename); this._renderer.setProperty(linkEl, 'onload', resolve); this._renderer.appendChild(this.head, linkEl); this.themeLinks = [...this.themeLinks, linkEl]; }) } }