From 03c077ee2f403ba03b768ed7fcca6736665e1e0b Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Thu, 28 May 2020 23:30:40 +0300 Subject: [PATCH] feat: add a lazy style handler and provider factory --- .../theme-shared/src/lib/handlers/index.ts | 1 + .../src/lib/handlers/lazy-style.handler.ts | 96 +++++++++++++++++++ .../src/lib/tests/lazy-style.handler.spec.ts | 66 +++++++++++++ 3 files changed, 163 insertions(+) create mode 100644 npm/ng-packs/packages/theme-shared/src/lib/handlers/lazy-style.handler.ts create mode 100644 npm/ng-packs/packages/theme-shared/src/lib/tests/lazy-style.handler.spec.ts diff --git a/npm/ng-packs/packages/theme-shared/src/lib/handlers/index.ts b/npm/ng-packs/packages/theme-shared/src/lib/handlers/index.ts index 7b46abdd69..11eb07398e 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/handlers/index.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/handlers/index.ts @@ -1 +1,2 @@ export * from './error.handler'; +export * from './lazy-style.handler'; diff --git a/npm/ng-packs/packages/theme-shared/src/lib/handlers/lazy-style.handler.ts b/npm/ng-packs/packages/theme-shared/src/lib/handlers/lazy-style.handler.ts new file mode 100644 index 0000000000..11fe0f0da8 --- /dev/null +++ b/npm/ng-packs/packages/theme-shared/src/lib/handlers/lazy-style.handler.ts @@ -0,0 +1,96 @@ +import { + getLocaleDirection, + LazyLoadService, + LOADING_STRATEGY, + LocalizationService, +} from '@abp/ng.core'; +import { Injectable, Injector } from '@angular/core'; +import { map, startWith } from 'rxjs/operators'; +import { BOOTSTRAP } from '../constants/styles'; +import { LocaleDirection } from '../models/common'; +import { LAZY_STYLES } from '../tokens/lazy-styles.token'; + +@Injectable({ + providedIn: 'root', +}) +export class LazyStyleHandler { + private lazyLoad: LazyLoadService; + private styles: string[]; + private _dir: LocaleDirection = 'ltr'; + + set dir(dir: LocaleDirection) { + if (dir === this._dir) return; + + this.switchCSS(dir); + this.setHtmlDir(dir); + this._dir = dir; + } + + get dir(): LocaleDirection { + return this._dir; + } + + constructor(injector: Injector) { + this.setStyles(injector); + this.setLazyLoad(injector); + this.listenToLanguageChanges(injector); + } + + private getLoadedBootstrap(): LoadedStyle { + const href = createLazyStyleHref(BOOTSTRAP, this.dir); + const selector = `[href$="${href}"]`; + const link = document.querySelector(selector); + return { href, link }; + } + + private listenToLanguageChanges(injector: Injector) { + const l10n = injector.get(LocalizationService); + + // will always listen, no need to unsubscribe + l10n.languageChange + .pipe( + map(({ payload }) => payload), + startWith(l10n.currentLang), + ) + .subscribe(locale => { + this.dir = getLocaleDirection(locale); + }); + } + + private setHtmlDir(dir: LocaleDirection) { + document.querySelector('html').dir = dir; + } + + private setLazyLoad(injector: Injector) { + this.lazyLoad = injector.get(LazyLoadService); + const { href, link } = this.getLoadedBootstrap(); + this.lazyLoad.loaded.set(href, link); + } + + private setStyles(injector: Injector) { + this.styles = injector.get(LAZY_STYLES, [BOOTSTRAP]); + } + + private switchCSS(dir: LocaleDirection) { + this.styles.forEach(style => { + const oldHref = createLazyStyleHref(style, this.dir); + const newHref = createLazyStyleHref(style, dir); + + const strategy = LOADING_STRATEGY.PrependAnonymousStyleToHead(newHref); + this.lazyLoad.load(strategy).subscribe(() => this.lazyLoad.remove(oldHref)); + }); + } +} + +export function createLazyStyleHref(style: string, dir: string): string { + return style.replace(/{{\s*dir\s*}}/g, dir); +} + +export function initLazyStyleHandler(injector: Injector) { + return () => new LazyStyleHandler(injector); +} + +interface LoadedStyle { + href: string; + link: HTMLLinkElement; +} diff --git a/npm/ng-packs/packages/theme-shared/src/lib/tests/lazy-style.handler.spec.ts b/npm/ng-packs/packages/theme-shared/src/lib/tests/lazy-style.handler.spec.ts new file mode 100644 index 0000000000..5836bee474 --- /dev/null +++ b/npm/ng-packs/packages/theme-shared/src/lib/tests/lazy-style.handler.spec.ts @@ -0,0 +1,66 @@ +import { LazyLoadService, LOADING_STRATEGY, LocalizationService } from '@abp/ng.core'; +import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest'; +import { EMPTY, of } from 'rxjs'; +import { BOOTSTRAP } from '../constants/styles'; +import { createLazyStyleHref, initLazyStyleHandler, LazyStyleHandler } from '../handlers'; + +const languageChange = of({ payload: 'en' }); + +describe('LazyStyleHandler', () => { + let spectator: SpectatorService; + let handler: LazyStyleHandler; + let lazyLoad: LazyLoadService; + + const createService = createServiceFactory({ + service: LazyStyleHandler, + providers: [ + { + provide: LocalizationService, + useValue: { currentLang: 'en', languageChange }, + }, + ], + }); + + beforeEach(() => { + spectator = createService(); + handler = spectator.service; + lazyLoad = handler['lazyLoad']; + }); + + describe('#dir', () => { + it('should initially be "ltr"', () => { + expect(handler.dir).toBe('ltr'); + }); + + it('should set bootstrap to rtl', () => { + const oldHref = createLazyStyleHref(BOOTSTRAP, 'ltr'); + const newHref = createLazyStyleHref(BOOTSTRAP, 'rtl'); + lazyLoad.loaded.set(newHref, null); // avoid actual loading + const load = jest.spyOn(lazyLoad, 'load'); + const remove = jest.spyOn(lazyLoad, 'remove'); + const strategy = LOADING_STRATEGY.PrependAnonymousStyleToHead(newHref); + + handler.dir = 'rtl'; + + expect(load).toHaveBeenCalledWith(strategy); + expect(remove).toHaveBeenCalledWith(oldHref); + }); + }); +}); + +describe('initLazyStyleHandler', () => { + it('should return a LazyStyleHandler factory', () => { + const generator = (function*() { + yield undefined; // LAZY_STYLES + yield { loaded: new Map() }; // LazyLoadService + yield { currentLang: 'en', languageChange: EMPTY }; // LocalizationService + })(); + + const injector = { + get: () => generator.next().value as any, + }; + const factory = initLazyStyleHandler(injector); + + expect(factory()).toBeInstanceOf(LazyStyleHandler); + }); +});