From 86fee705ddb917898bcb92185456f4bbbc6215c7 Mon Sep 17 00:00:00 2001 From: ChristopherPHolder Date: Wed, 9 Oct 2024 22:14:14 +0200 Subject: [PATCH 1/5] feat(lib): lazy url config --- .../src/app/app.config.server.ts | 13 +++--- .../ngx-fast-icon-demo/src/app/app.config.ts | 9 +++- .../src/lib/fast-svg.component.ts | 45 +++++++++++-------- .../src/lib/token/svg-load.strategy.model.ts | 2 +- .../src/lib/token/svg-load.strategy.ts | 6 +-- .../src/lib/token/svg-options.model.ts | 4 +- 6 files changed, 49 insertions(+), 30 deletions(-) diff --git a/packages/ngx-fast-icon-demo/src/app/app.config.server.ts b/packages/ngx-fast-icon-demo/src/app/app.config.server.ts index c49bf95..f7ab042 100644 --- a/packages/ngx-fast-icon-demo/src/app/app.config.server.ts +++ b/packages/ngx-fast-icon-demo/src/app/app.config.server.ts @@ -4,7 +4,7 @@ import { readFileSync } from 'node:fs'; import { mergeApplicationConfig, ApplicationConfig, Injectable } from '@angular/core'; import { provideServerRendering } from '@angular/platform-server'; -import { Observable, of } from 'rxjs'; +import { map, Observable } from 'rxjs'; import { provideFastSVG, SvgLoadStrategy } from '@push-based/ngx-fast-svg'; @@ -12,10 +12,13 @@ import { appConfig } from './app.config'; @Injectable() export class SvgLoadStrategySsr implements SvgLoadStrategy { - load(url: string): Observable { - const iconPath = join(process.cwd(), 'packages', 'ngx-fast-icon-demo', 'src', url); - const iconSVG = readFileSync(iconPath, 'utf8') - return of(iconSVG); + load(url$: Observable): Observable { + return url$.pipe( + map((url) => { + const iconPath = join(process.cwd(), 'packages', 'ngx-fast-icon-demo', 'src', url); + return readFileSync(iconPath, 'utf8') + }) + ) } } diff --git a/packages/ngx-fast-icon-demo/src/app/app.config.ts b/packages/ngx-fast-icon-demo/src/app/app.config.ts index d8f8674..87edee3 100644 --- a/packages/ngx-fast-icon-demo/src/app/app.config.ts +++ b/packages/ngx-fast-icon-demo/src/app/app.config.ts @@ -14,6 +14,13 @@ import { provideAngularSvgIcon } from 'angular-svg-icon'; import { provideIonicAngular } from '@ionic/angular/standalone'; import { appRoutes } from './app.routes'; +import { map, Observable, timer } from 'rxjs'; + +class LoaderStrategy { + load(name: string): Observable { + return timer(1000).pipe(map(() => `assets/svg-icons/${name}.svg`)) + }; +} export const appConfig: ApplicationConfig = { providers: [ @@ -32,7 +39,7 @@ export const appConfig: ApplicationConfig = { provideAngularSvgIcon(), provideIonicAngular({}), provideFastSVG({ - url: (name: string) => `assets/svg-icons/${name}.svg`, + url: (name: string) => timer(10000).pipe(map(() => `assets/svg-icons/${name}.svg`)), }), ], }; diff --git a/packages/ngx-fast-lib/src/lib/fast-svg.component.ts b/packages/ngx-fast-lib/src/lib/fast-svg.component.ts index 8f1f5dd..7212437 100644 --- a/packages/ngx-fast-lib/src/lib/fast-svg.component.ts +++ b/packages/ngx-fast-lib/src/lib/fast-svg.component.ts @@ -4,18 +4,19 @@ import { AfterViewInit, ChangeDetectionStrategy, Component, + effect, ElementRef, + inject, Injector, + input, OnDestroy, PLATFORM_ID, - Renderer2, - effect, - inject, - input, - untracked, + Renderer2 } from '@angular/core'; import { getZoneUnPatchedApi } from './internal/get-zone-unpatched-api'; import { SvgRegistry } from './svg-registry.service'; +import { toObservable, toSignal } from '@angular/core/rxjs-interop'; +import { of, switchMap } from 'rxjs'; /** * getZoneUnPatchedApi @@ -110,6 +111,11 @@ export class FastSvgComponent implements AfterViewInit, OnDestroy { width = input(''); height = input(''); + #url = toSignal(toObservable(this.name).pipe(switchMap((name) => { + const url = this.registry.url(name); + return typeof url === 'string' ? of(url) : url; + }))) + // When the browser loaded the svg resource we trigger the caching mechanism // re-fetch -> cache-hit -> get SVG -> cache in DOM loadedListener = () => { @@ -142,7 +148,6 @@ export class FastSvgComponent implements AfterViewInit, OnDestroy { (onCleanup) => { const name = this.name(); - untracked(() => { if (!name) { throw new Error('svg component needs a name to operate'); } @@ -153,32 +158,35 @@ export class FastSvgComponent implements AfterViewInit, OnDestroy { if (!this.registry.isSvgCached(name)) { /** CSR - Browser native lazy loading hack - + We use an img element here to leverage the browsers native features: - lazy loading (loading="lazy") to only load the svg that are actually visible - priority hints to down prioritize the fetch to avoid delaying the LCP - + While the SVG is loading we display a fallback SVG. After the image is loaded we remove it from the DOM. (IMG load event) When the new svg arrives we append it to the template. - + Note: - the image is styled with display none. this prevents any loading of the resource ever. on component bootstrap we decide what we want to do. when we remove display none it performs the browser native behavior - - the image has 0 height and with and containment as well as contnet-visibility to reduce any performance impact - - + - the image has 0 height and with and containment as well as content-visibility to reduce any performance impact + + Edge cases: - only resources that are not loaded in the current session of the browser will get lazy loaded (same URL to trigger loading is not possible) - already loaded resources will get emitted from the cache immediately, even if loading is set to lazy :o - the image needs to have display other than none */ - const i = this.getImg(this.registry.url(name)); - this.renderer.appendChild(this.element.nativeElement, i); - - // get img - img = elem.querySelector('img') as HTMLImageElement; - addEventListener(img, 'load', this.loadedListener); + const url = this.#url(); + if (url) { + const i = this.getImg(url); + this.renderer.appendChild(this.element.nativeElement, i); + + // get img + img = elem.querySelector('img') as HTMLImageElement; + addEventListener(img, 'load', this.loadedListener); + } } // Listen to svg changes @@ -225,7 +233,6 @@ export class FastSvgComponent implements AfterViewInit, OnDestroy { img.removeEventListener('load', this.loadedListener); } }); - }); }, { injector: this.injector } ); diff --git a/packages/ngx-fast-lib/src/lib/token/svg-load.strategy.model.ts b/packages/ngx-fast-lib/src/lib/token/svg-load.strategy.model.ts index c82bb07..5004c38 100644 --- a/packages/ngx-fast-lib/src/lib/token/svg-load.strategy.model.ts +++ b/packages/ngx-fast-lib/src/lib/token/svg-load.strategy.model.ts @@ -3,5 +3,5 @@ import { Injectable } from '@angular/core'; @Injectable() export abstract class SvgLoadStrategy { - abstract load(url: string): Observable; + abstract load(url: string | Observable): Observable; } diff --git a/packages/ngx-fast-lib/src/lib/token/svg-load.strategy.ts b/packages/ngx-fast-lib/src/lib/token/svg-load.strategy.ts index ce06eea..d8cfb7a 100644 --- a/packages/ngx-fast-lib/src/lib/token/svg-load.strategy.ts +++ b/packages/ngx-fast-lib/src/lib/token/svg-load.strategy.ts @@ -1,11 +1,11 @@ -import { from, Observable } from 'rxjs'; +import { from, Observable, switchMap } from 'rxjs'; import { getZoneUnPatchedApi } from '../internal/get-zone-unpatched-api'; import { SvgLoadStrategy } from "./svg-load.strategy.model"; export class SvgLoadStrategyImpl extends SvgLoadStrategy { fetch = getZoneUnPatchedApi('fetch', window as any); - load(url: string): Observable { - return from(fetch(url).then((res) => (!res.ok ? '' : res.text()))); + load(url$: Observable): Observable { + return url$.pipe(switchMap((url) =>from(fetch(url).then((res) => (!res.ok ? '' : res.text()))))); } } diff --git a/packages/ngx-fast-lib/src/lib/token/svg-options.model.ts b/packages/ngx-fast-lib/src/lib/token/svg-options.model.ts index d4ad7cc..72dc772 100644 --- a/packages/ngx-fast-lib/src/lib/token/svg-options.model.ts +++ b/packages/ngx-fast-lib/src/lib/token/svg-options.model.ts @@ -1,5 +1,7 @@ +import { Observable } from 'rxjs'; + export type SvgOptions = { - url: (name: string) => string; + url: (name: string) => string | Observable; defaultSize?: string; suspenseSvgString?: string; }; From c8de395f97df5533013e97c0be5bc678b1472098 Mon Sep 17 00:00:00 2001 From: ChristopherPHolder Date: Fri, 11 Oct 2024 22:04:34 +0200 Subject: [PATCH 2/5] feat(lib): lazy url config --- .../src/app/app.config.server.ts | 17 +++++++---------- .../ngx-fast-icon-demo/src/app/app.config.ts | 19 +++++++++++-------- packages/ngx-fast-lib/src/index.ts | 2 +- .../src/lib/fast-svg.component.ts | 3 +-- .../src/lib/svg-registry.service.ts | 4 ++-- .../src/lib/token/svg-load.strategy.model.ts | 5 ++++- .../src/lib/token/svg-load.strategy.ts | 8 ++++---- .../src/lib/token/svg-options.model.ts | 4 +--- 8 files changed, 31 insertions(+), 31 deletions(-) diff --git a/packages/ngx-fast-icon-demo/src/app/app.config.server.ts b/packages/ngx-fast-icon-demo/src/app/app.config.server.ts index f7ab042..39fad81 100644 --- a/packages/ngx-fast-icon-demo/src/app/app.config.server.ts +++ b/packages/ngx-fast-icon-demo/src/app/app.config.server.ts @@ -1,24 +1,21 @@ import { join } from 'node:path'; import { readFileSync } from 'node:fs'; -import { mergeApplicationConfig, ApplicationConfig, Injectable } from '@angular/core'; +import { ApplicationConfig, Injectable, mergeApplicationConfig } from '@angular/core'; import { provideServerRendering } from '@angular/platform-server'; -import { map, Observable } from 'rxjs'; +import { Observable, of } from 'rxjs'; import { provideFastSVG, SvgLoadStrategy } from '@push-based/ngx-fast-svg'; import { appConfig } from './app.config'; @Injectable() -export class SvgLoadStrategySsr implements SvgLoadStrategy { - load(url$: Observable): Observable { - return url$.pipe( - map((url) => { - const iconPath = join(process.cwd(), 'packages', 'ngx-fast-icon-demo', 'src', url); - return readFileSync(iconPath, 'utf8') - }) - ) +export class SvgLoadStrategySsr extends SvgLoadStrategy { + load(url: string): Observable { + const iconPath = join(process.cwd(), 'packages', 'ngx-fast-icon-demo', 'src', url); + const iconSVG = readFileSync(iconPath, 'utf8') + return of(iconSVG); } } diff --git a/packages/ngx-fast-icon-demo/src/app/app.config.ts b/packages/ngx-fast-icon-demo/src/app/app.config.ts index 87edee3..9baed24 100644 --- a/packages/ngx-fast-icon-demo/src/app/app.config.ts +++ b/packages/ngx-fast-icon-demo/src/app/app.config.ts @@ -1,4 +1,4 @@ -import { ApplicationConfig } from '@angular/core'; +import { ApplicationConfig, Injectable } from '@angular/core'; import { provideRouter, withComponentInputBinding, @@ -9,17 +9,19 @@ import { provideHttpClient, withFetch } from '@angular/common/http'; import { provideClientHydration } from '@angular/platform-browser'; import { provideAnimations } from '@angular/platform-browser/animations'; -import { provideFastSVG } from '@push-based/ngx-fast-svg'; +import { provideFastSVG, SvgLoadStrategyImpl } from '@push-based/ngx-fast-svg'; import { provideAngularSvgIcon } from 'angular-svg-icon'; import { provideIonicAngular } from '@ionic/angular/standalone'; import { appRoutes } from './app.routes'; -import { map, Observable, timer } from 'rxjs'; +import { Observable, of, switchMap, timer } from 'rxjs'; -class LoaderStrategy { - load(name: string): Observable { - return timer(1000).pipe(map(() => `assets/svg-icons/${name}.svg`)) - }; +@Injectable({ providedIn: 'root' }) +export class ConfigService extends SvgLoadStrategyImpl { + lazy$ = timer(10_000) + override config(url: string): Observable { + return this.lazy$.pipe(switchMap(() => of(url))) + } } export const appConfig: ApplicationConfig = { @@ -39,7 +41,8 @@ export const appConfig: ApplicationConfig = { provideAngularSvgIcon(), provideIonicAngular({}), provideFastSVG({ - url: (name: string) => timer(10000).pipe(map(() => `assets/svg-icons/${name}.svg`)), + url: (name: string) => `assets/svg-icons/${name}.svg`, + svgLoadStrategy: ConfigService }), ], }; diff --git a/packages/ngx-fast-lib/src/index.ts b/packages/ngx-fast-lib/src/index.ts index 9a936ad..e92e037 100644 --- a/packages/ngx-fast-lib/src/index.ts +++ b/packages/ngx-fast-lib/src/index.ts @@ -2,7 +2,7 @@ export * from './lib/token/svg-options.model'; export * from './lib/token/svg-options.token'; export * from './lib/token/svg-load.strategy.model'; -export * from './lib/token/svg-load.strategy'; +export { SvgLoadStrategyImpl } from './lib/token/svg-load.strategy'; // service export * from './lib/svg-registry.service'; // component diff --git a/packages/ngx-fast-lib/src/lib/fast-svg.component.ts b/packages/ngx-fast-lib/src/lib/fast-svg.component.ts index 7212437..626cb75 100644 --- a/packages/ngx-fast-lib/src/lib/fast-svg.component.ts +++ b/packages/ngx-fast-lib/src/lib/fast-svg.component.ts @@ -112,8 +112,7 @@ export class FastSvgComponent implements AfterViewInit, OnDestroy { height = input(''); #url = toSignal(toObservable(this.name).pipe(switchMap((name) => { - const url = this.registry.url(name); - return typeof url === 'string' ? of(url) : url; + return this.registry.url(name); }))) // When the browser loaded the svg resource we trigger the caching mechanism diff --git a/packages/ngx-fast-lib/src/lib/svg-registry.service.ts b/packages/ngx-fast-lib/src/lib/svg-registry.service.ts index 3a0ae55..b5dd10e 100644 --- a/packages/ngx-fast-lib/src/lib/svg-registry.service.ts +++ b/packages/ngx-fast-lib/src/lib/svg-registry.service.ts @@ -4,7 +4,7 @@ import { BehaviorSubject, map, Observable } from 'rxjs'; import { SvgOptionsToken } from './token/svg-options.token'; import { suspenseSvg } from './token/default-token-values'; import { SvgLoadStrategy } from './token/svg-load.strategy.model'; -import { SvgLoadStrategyImpl } from "./token/svg-load.strategy"; +import { SvgLoadStrategyImpl } from './token/svg-load.strategy'; // @TODO compose svg in 1 sprite and fetch by id as before @@ -69,7 +69,7 @@ export class SvgRegistry { public defaultSize = this.svgOptions?.defaultSize || '24'; private _defaultViewBox = `0 0 ${this.defaultSize} ${this.defaultSize}`; - public url = this.svgOptions.url; + public url = (name: string) => this.svgLoadStrategy.config(this.svgOptions.url(name)); constructor() { // configure suspense svg diff --git a/packages/ngx-fast-lib/src/lib/token/svg-load.strategy.model.ts b/packages/ngx-fast-lib/src/lib/token/svg-load.strategy.model.ts index 5004c38..c833588 100644 --- a/packages/ngx-fast-lib/src/lib/token/svg-load.strategy.model.ts +++ b/packages/ngx-fast-lib/src/lib/token/svg-load.strategy.model.ts @@ -1,7 +1,10 @@ -import { Observable } from 'rxjs'; +import { Observable, of } from 'rxjs'; import { Injectable } from '@angular/core'; @Injectable() export abstract class SvgLoadStrategy { abstract load(url: string | Observable): Observable; + config(url: string): Observable { + return of(url) + }; } diff --git a/packages/ngx-fast-lib/src/lib/token/svg-load.strategy.ts b/packages/ngx-fast-lib/src/lib/token/svg-load.strategy.ts index d8cfb7a..7a93696 100644 --- a/packages/ngx-fast-lib/src/lib/token/svg-load.strategy.ts +++ b/packages/ngx-fast-lib/src/lib/token/svg-load.strategy.ts @@ -1,11 +1,11 @@ -import { from, Observable, switchMap } from 'rxjs'; +import { from, Observable } from 'rxjs'; import { getZoneUnPatchedApi } from '../internal/get-zone-unpatched-api'; -import { SvgLoadStrategy } from "./svg-load.strategy.model"; +import { SvgLoadStrategy } from './svg-load.strategy.model'; export class SvgLoadStrategyImpl extends SvgLoadStrategy { fetch = getZoneUnPatchedApi('fetch', window as any); - load(url$: Observable): Observable { - return url$.pipe(switchMap((url) =>from(fetch(url).then((res) => (!res.ok ? '' : res.text()))))); + load(url: string): Observable { + return from(fetch(url).then((res) => (!res.ok ? '' : res.text()))); } } diff --git a/packages/ngx-fast-lib/src/lib/token/svg-options.model.ts b/packages/ngx-fast-lib/src/lib/token/svg-options.model.ts index 72dc772..d4ad7cc 100644 --- a/packages/ngx-fast-lib/src/lib/token/svg-options.model.ts +++ b/packages/ngx-fast-lib/src/lib/token/svg-options.model.ts @@ -1,7 +1,5 @@ -import { Observable } from 'rxjs'; - export type SvgOptions = { - url: (name: string) => string | Observable; + url: (name: string) => string; defaultSize?: string; suspenseSvgString?: string; }; From 16c62061780f7830360143cdb52afa674d2f1743 Mon Sep 17 00:00:00 2001 From: ChristopherPHolder Date: Sun, 13 Oct 2024 12:51:36 +0200 Subject: [PATCH 3/5] feat(lib): lazy url config --- .../src/app/app.component.ts | 8 ++-- .../src/app/app.config.server.ts | 16 +++---- .../ngx-fast-icon-demo/src/app/app.config.ts | 14 +----- packages/ngx-fast-lib/README.md | 45 ++++++++++++++++--- .../src/lib/svg-registry.service.ts | 2 +- .../src/lib/token/svg-load.strategy.model.ts | 8 ++-- .../src/lib/token/svg-load.strategy.ts | 14 ++++-- 7 files changed, 66 insertions(+), 41 deletions(-) diff --git a/packages/ngx-fast-icon-demo/src/app/app.component.ts b/packages/ngx-fast-icon-demo/src/app/app.component.ts index 0b2a7b3..8eb4183 100644 --- a/packages/ngx-fast-icon-demo/src/app/app.component.ts +++ b/packages/ngx-fast-icon-demo/src/app/app.component.ts @@ -1,10 +1,10 @@ import { Component, inject, PLATFORM_ID } from '@angular/core'; -import { ActivatedRoute, NavigationEnd, Router, RouterOutlet } from '@angular/router'; +import { NavigationEnd, Router, RouterOutlet } from '@angular/router'; import { filter, map, Observable, startWith } from 'rxjs'; -import {MediaMatcher} from '@angular/cdk/layout'; -import {ShellComponent} from './misc/shell.component'; -import {AsyncPipe, isPlatformServer} from '@angular/common'; +import { MediaMatcher } from '@angular/cdk/layout'; +import { ShellComponent } from './misc/shell.component'; +import { AsyncPipe, isPlatformServer } from '@angular/common'; @Component({ selector: 'ngx-fast-icon-root', diff --git a/packages/ngx-fast-icon-demo/src/app/app.config.server.ts b/packages/ngx-fast-icon-demo/src/app/app.config.server.ts index 39fad81..e5118af 100644 --- a/packages/ngx-fast-icon-demo/src/app/app.config.server.ts +++ b/packages/ngx-fast-icon-demo/src/app/app.config.server.ts @@ -1,21 +1,21 @@ import { join } from 'node:path'; -import { readFileSync } from 'node:fs'; +import { readFile } from 'node:fs/promises'; +import { cwd } from 'node:process'; import { ApplicationConfig, Injectable, mergeApplicationConfig } from '@angular/core'; import { provideServerRendering } from '@angular/platform-server'; -import { Observable, of } from 'rxjs'; +import { from, Observable, of, switchMap } from 'rxjs'; import { provideFastSVG, SvgLoadStrategy } from '@push-based/ngx-fast-svg'; import { appConfig } from './app.config'; @Injectable() -export class SvgLoadStrategySsr extends SvgLoadStrategy { - load(url: string): Observable { - const iconPath = join(process.cwd(), 'packages', 'ngx-fast-icon-demo', 'src', url); - const iconSVG = readFileSync(iconPath, 'utf8') - return of(iconSVG); +export class SvgLoadStrategySsr implements SvgLoadStrategy { + config = (url: string) => of(join(cwd(), 'packages', 'ngx-fast-icon-demo', 'src', 'assets', 'svg-icons', url)); + load(iconPath$: Observable) { + return iconPath$.pipe(switchMap((iconPath) => from(readFile(iconPath, { encoding: 'utf8' })))) } } @@ -24,7 +24,7 @@ const serverConfig: ApplicationConfig = { provideServerRendering(), provideFastSVG({ svgLoadStrategy: SvgLoadStrategySsr, - url: (name: string) => `assets/svg-icons/${name}.svg`, + url: (name: string) => `${name}.svg`, }), ], }; diff --git a/packages/ngx-fast-icon-demo/src/app/app.config.ts b/packages/ngx-fast-icon-demo/src/app/app.config.ts index 9baed24..d8f8674 100644 --- a/packages/ngx-fast-icon-demo/src/app/app.config.ts +++ b/packages/ngx-fast-icon-demo/src/app/app.config.ts @@ -1,4 +1,4 @@ -import { ApplicationConfig, Injectable } from '@angular/core'; +import { ApplicationConfig } from '@angular/core'; import { provideRouter, withComponentInputBinding, @@ -9,20 +9,11 @@ import { provideHttpClient, withFetch } from '@angular/common/http'; import { provideClientHydration } from '@angular/platform-browser'; import { provideAnimations } from '@angular/platform-browser/animations'; -import { provideFastSVG, SvgLoadStrategyImpl } from '@push-based/ngx-fast-svg'; +import { provideFastSVG } from '@push-based/ngx-fast-svg'; import { provideAngularSvgIcon } from 'angular-svg-icon'; import { provideIonicAngular } from '@ionic/angular/standalone'; import { appRoutes } from './app.routes'; -import { Observable, of, switchMap, timer } from 'rxjs'; - -@Injectable({ providedIn: 'root' }) -export class ConfigService extends SvgLoadStrategyImpl { - lazy$ = timer(10_000) - override config(url: string): Observable { - return this.lazy$.pipe(switchMap(() => of(url))) - } -} export const appConfig: ApplicationConfig = { providers: [ @@ -42,7 +33,6 @@ export const appConfig: ApplicationConfig = { provideIonicAngular({}), provideFastSVG({ url: (name: string) => `assets/svg-icons/${name}.svg`, - svgLoadStrategy: ConfigService }), ], }; diff --git a/packages/ngx-fast-lib/README.md b/packages/ngx-fast-lib/README.md index 64b9a3e..38d98a4 100644 --- a/packages/ngx-fast-lib/README.md +++ b/packages/ngx-fast-lib/README.md @@ -102,12 +102,14 @@ During setup phase you can provide additional optional settings such as: svgLoadStrategy?: Type; ``` -`svgLoadStrategy` can be any injectable class that has `load` method that accepts url and returns observable string: +`svgLoadStrategy` can be any injectable class that has `config` that excepts method that accepts url and returns observable string, +and `load` which accepts the configured url as an observable and returns the svg as an observable string. ```typescript @Injectable() export abstract class SvgLoadStrategy { - abstract load(url: string): Observable; + abstract config(url: string): Observable; + abstract load(url: Observable): Observable; } ``` @@ -164,10 +166,9 @@ You can provide your own SSR loading strategy that can look like this: ```typescript @Injectable() export class SvgLoadStrategySsr implements SvgLoadStrategy { - load(url: string): Observable { - const iconPath = join(process.cwd(), 'dist', 'app-name', 'browser', url); - const iconSVG = readFileSync(iconPath, 'utf8'); - return of(iconSVG); + config = (url: string) => of(join(cwd(), 'path', 'to', 'svg', 'assets', url)); + load(iconPath$: Observable) { + return iconPath$.pipe(switchMap((iconPath) => from(readFile(iconPath, { encoding: 'utf8' })))); } } ``` @@ -187,7 +188,7 @@ And then just provide it in you server module. providers: [ provideFastSVG({ svgLoadStrategy: SvgLoadStrategySsr, - url: (name: string) => `assets/svg-icons/${name}.svg`, + url: (name: string) => `${name}.svg`, }), ], bootstrap: [AppComponent], @@ -195,6 +196,36 @@ And then just provide it in you server module. export class AppServerModule {} ``` +#### Providing a lazy configuration + +If you need to provide a lazy configuration you can use the config method in the `SvgLoadStrategy`: + +```typescript +@Injectable() +class LazyConfigSvgLoadStrategy extends SvgLoadStrategyImpl { + lazyConfigService = inject(SvgConfigService); + override config(url: string) { + return this.lazyConfigService.urlConfig(url); + } +} +``` + +And pass it to the provider function: + +```typescript +import { provideFastSVG } from '@push-based/ngx-fast-svg'; + +bootstrapApplication(AppComponent, { + providers: [ + // ... other providers + provideFastSVG({ + url: (name: string): Observable => inject(ConfigService).svgUrl(name), + svgLoadStrategy: LazyConfigSvgLoadStrategy, + }) + ] +}); +``` + ## Features ### :sloth: Lazy loading for SVGs diff --git a/packages/ngx-fast-lib/src/lib/svg-registry.service.ts b/packages/ngx-fast-lib/src/lib/svg-registry.service.ts index b5dd10e..501e529 100644 --- a/packages/ngx-fast-lib/src/lib/svg-registry.service.ts +++ b/packages/ngx-fast-lib/src/lib/svg-registry.service.ts @@ -108,7 +108,7 @@ export class SvgRegistry { // trigger fetch this.svgLoadStrategy - .load(this.svgOptions.url(svgName)) + .load(this.url(svgName)) .subscribe({ next: (body: string) => this.cacheSvgInDOM(svgId, body), error: console.error diff --git a/packages/ngx-fast-lib/src/lib/token/svg-load.strategy.model.ts b/packages/ngx-fast-lib/src/lib/token/svg-load.strategy.model.ts index c833588..b7d527e 100644 --- a/packages/ngx-fast-lib/src/lib/token/svg-load.strategy.model.ts +++ b/packages/ngx-fast-lib/src/lib/token/svg-load.strategy.model.ts @@ -1,10 +1,8 @@ -import { Observable, of } from 'rxjs'; +import { Observable } from 'rxjs'; import { Injectable } from '@angular/core'; @Injectable() export abstract class SvgLoadStrategy { - abstract load(url: string | Observable): Observable; - config(url: string): Observable { - return of(url) - }; + abstract config(url: string): Observable; + abstract load(url: Observable): Observable; } diff --git a/packages/ngx-fast-lib/src/lib/token/svg-load.strategy.ts b/packages/ngx-fast-lib/src/lib/token/svg-load.strategy.ts index 7a93696..a3b2538 100644 --- a/packages/ngx-fast-lib/src/lib/token/svg-load.strategy.ts +++ b/packages/ngx-fast-lib/src/lib/token/svg-load.strategy.ts @@ -1,11 +1,17 @@ -import { from, Observable } from 'rxjs'; +import { from, Observable, of, switchMap } from 'rxjs'; import { getZoneUnPatchedApi } from '../internal/get-zone-unpatched-api'; import { SvgLoadStrategy } from './svg-load.strategy.model'; +import { Injectable } from '@angular/core'; -export class SvgLoadStrategyImpl extends SvgLoadStrategy { +@Injectable() +export class SvgLoadStrategyImpl implements SvgLoadStrategy { fetch = getZoneUnPatchedApi('fetch', window as any); - load(url: string): Observable { - return from(fetch(url).then((res) => (!res.ok ? '' : res.text()))); + load(url$: Observable): Observable { + return url$.pipe(switchMap((url) => { + return from(fetch(url).then((res) => (!res.ok ? '' : res.text()))); + })); } + + config = (url: string) => of(url); } From 545a81d04a17d6fb53145c3f652bc2f4d32485ce Mon Sep 17 00:00:00 2001 From: ChristopherPHolder Date: Sun, 13 Oct 2024 13:15:42 +0200 Subject: [PATCH 4/5] feat(lib): lazy url config --- README.md | 36 ++++++++++++++++++++++++++++++--- packages/ngx-fast-lib/README.md | 13 +++++++----- 2 files changed, 41 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 389bd86..90ed3ec 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ This library covers next aspects that developers should consider for their proje - SVG reusability - Optimized bundle size - SSR -- Edge ready (only edge save APIs are used) +- Edge ready (only edge safe APIs are used) ## Getting started @@ -103,7 +103,8 @@ During setup phase you can provide additional optional settings such as: svgLoadStrategy?: Type; ``` -`svgLoadStrategy` can be any injectable class that has `load` method that accepts url and returns observable string: +`svgLoadStrategy` can be any injectable class that has `config` that excepts method that accepts url and returns observable string, +and `load` which accepts the configured url as an observable and returns the svg as an observable string. ```typescript @Injectable() @@ -196,6 +197,36 @@ And then just provide it in you server module. export class AppServerModule {} ``` +#### Providing a lazy configuration + +If you need to provide a lazy configuration you can use the config method in the `SvgLoadStrategy`: + +```typescript +@Injectable() +class LazyConfigSvgLoadStrategy extends SvgLoadStrategyImpl { + dummyLazyConfig$ = timer(5_000).pipe(map(() => 'assets/svg-icons')) + override config(url: string): Observable { + return this.dummyLazyConfig$.pipe(map((svgConfig) => `${svgConfig}/${url}`)); + } +} +``` + +And pass it to the provider function: + +```typescript +import { provideFastSVG } from '@push-based/ngx-fast-svg'; + +bootstrapApplication(AppComponent, { + providers: [ + // ... other providers + provideFastSVG({ + url: (name: string) => `${name}.svg`, + svgLoadStrategy: LazyConfigSvgLoadStrategy, + }) + ] +}); +``` + ## Features ### :sloth: Lazy loading for SVGs @@ -273,4 +304,3 @@ To display (render) SVGs the browser takes time. We can reduce that time by addi --- made with ❤ by [push-based.io](https://www.push-based.io) - diff --git a/packages/ngx-fast-lib/README.md b/packages/ngx-fast-lib/README.md index 38d98a4..44dbe77 100644 --- a/packages/ngx-fast-lib/README.md +++ b/packages/ngx-fast-lib/README.md @@ -13,6 +13,7 @@ This library covers next aspects that developers should consider for their proje - SVG reusability - Optimized bundle size - SSR +- Edge ready (only edge safe APIs are used) ## Getting started @@ -166,7 +167,9 @@ You can provide your own SSR loading strategy that can look like this: ```typescript @Injectable() export class SvgLoadStrategySsr implements SvgLoadStrategy { - config = (url: string) => of(join(cwd(), 'path', 'to', 'svg', 'assets', url)); + config(url: string) { + return of(join(cwd(), 'path', 'to', 'svg', 'assets', url)); + } load(iconPath$: Observable) { return iconPath$.pipe(switchMap((iconPath) => from(readFile(iconPath, { encoding: 'utf8' })))); } @@ -203,9 +206,9 @@ If you need to provide a lazy configuration you can use the config method in the ```typescript @Injectable() class LazyConfigSvgLoadStrategy extends SvgLoadStrategyImpl { - lazyConfigService = inject(SvgConfigService); - override config(url: string) { - return this.lazyConfigService.urlConfig(url); + dummyLazyConfig$ = timer(5_000).pipe(map(() => 'assets/svg-icons')) + override config(url: string): Observable { + return this.dummyLazyConfig$.pipe(map((svgConfig) => `${svgConfig}/${url}`)); } } ``` @@ -219,7 +222,7 @@ bootstrapApplication(AppComponent, { providers: [ // ... other providers provideFastSVG({ - url: (name: string): Observable => inject(ConfigService).svgUrl(name), + url: (name: string) => `${name}.svg`, svgLoadStrategy: LazyConfigSvgLoadStrategy, }) ] From c4e3ff5873ffc711362551437a4a3708a501883e Mon Sep 17 00:00:00 2001 From: ChristopherPHolder Date: Sun, 13 Oct 2024 13:16:46 +0200 Subject: [PATCH 5/5] feat(lib): lazy url config --- packages/ngx-fast-icon-demo/src/app/app.config.server.ts | 4 +++- packages/ngx-fast-lib/src/lib/token/svg-load.strategy.ts | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/ngx-fast-icon-demo/src/app/app.config.server.ts b/packages/ngx-fast-icon-demo/src/app/app.config.server.ts index e5118af..46a052b 100644 --- a/packages/ngx-fast-icon-demo/src/app/app.config.server.ts +++ b/packages/ngx-fast-icon-demo/src/app/app.config.server.ts @@ -13,7 +13,9 @@ import { appConfig } from './app.config'; @Injectable() export class SvgLoadStrategySsr implements SvgLoadStrategy { - config = (url: string) => of(join(cwd(), 'packages', 'ngx-fast-icon-demo', 'src', 'assets', 'svg-icons', url)); + config(url: string) { + return of(join(cwd(), 'packages', 'ngx-fast-icon-demo', 'src', 'assets', 'svg-icons', url)); + } load(iconPath$: Observable) { return iconPath$.pipe(switchMap((iconPath) => from(readFile(iconPath, { encoding: 'utf8' })))) } diff --git a/packages/ngx-fast-lib/src/lib/token/svg-load.strategy.ts b/packages/ngx-fast-lib/src/lib/token/svg-load.strategy.ts index a3b2538..62f76f8 100644 --- a/packages/ngx-fast-lib/src/lib/token/svg-load.strategy.ts +++ b/packages/ngx-fast-lib/src/lib/token/svg-load.strategy.ts @@ -13,5 +13,7 @@ export class SvgLoadStrategyImpl implements SvgLoadStrategy { })); } - config = (url: string) => of(url); + config(url: string) { + return of(url); + } }