Skip to content

Commit 86fee70

Browse files
feat(lib): lazy url config
1 parent 376a952 commit 86fee70

File tree

6 files changed

+49
-30
lines changed

6 files changed

+49
-30
lines changed

packages/ngx-fast-icon-demo/src/app/app.config.server.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,21 @@ import { readFileSync } from 'node:fs';
44
import { mergeApplicationConfig, ApplicationConfig, Injectable } from '@angular/core';
55
import { provideServerRendering } from '@angular/platform-server';
66

7-
import { Observable, of } from 'rxjs';
7+
import { map, Observable } from 'rxjs';
88

99
import { provideFastSVG, SvgLoadStrategy } from '@push-based/ngx-fast-svg';
1010

1111
import { appConfig } from './app.config';
1212

1313
@Injectable()
1414
export class SvgLoadStrategySsr implements SvgLoadStrategy {
15-
load(url: string): Observable<string> {
16-
const iconPath = join(process.cwd(), 'packages', 'ngx-fast-icon-demo', 'src', url);
17-
const iconSVG = readFileSync(iconPath, 'utf8')
18-
return of(iconSVG);
15+
load(url$: Observable<string>): Observable<string> {
16+
return url$.pipe(
17+
map((url) => {
18+
const iconPath = join(process.cwd(), 'packages', 'ngx-fast-icon-demo', 'src', url);
19+
return readFileSync(iconPath, 'utf8')
20+
})
21+
)
1922
}
2023
}
2124

packages/ngx-fast-icon-demo/src/app/app.config.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,13 @@ import { provideAngularSvgIcon } from 'angular-svg-icon';
1414
import { provideIonicAngular } from '@ionic/angular/standalone';
1515

1616
import { appRoutes } from './app.routes';
17+
import { map, Observable, timer } from 'rxjs';
18+
19+
class LoaderStrategy {
20+
load(name: string): Observable<string> {
21+
return timer(1000).pipe(map(() => `assets/svg-icons/${name}.svg`))
22+
};
23+
}
1724

1825
export const appConfig: ApplicationConfig = {
1926
providers: [
@@ -32,7 +39,7 @@ export const appConfig: ApplicationConfig = {
3239
provideAngularSvgIcon(),
3340
provideIonicAngular({}),
3441
provideFastSVG({
35-
url: (name: string) => `assets/svg-icons/${name}.svg`,
42+
url: (name: string) => timer(10000).pipe(map(() => `assets/svg-icons/${name}.svg`)),
3643
}),
3744
],
3845
};

packages/ngx-fast-lib/src/lib/fast-svg.component.ts

Lines changed: 26 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,19 @@ import {
44
AfterViewInit,
55
ChangeDetectionStrategy,
66
Component,
7+
effect,
78
ElementRef,
9+
inject,
810
Injector,
11+
input,
912
OnDestroy,
1013
PLATFORM_ID,
11-
Renderer2,
12-
effect,
13-
inject,
14-
input,
15-
untracked,
14+
Renderer2
1615
} from '@angular/core';
1716
import { getZoneUnPatchedApi } from './internal/get-zone-unpatched-api';
1817
import { SvgRegistry } from './svg-registry.service';
18+
import { toObservable, toSignal } from '@angular/core/rxjs-interop';
19+
import { of, switchMap } from 'rxjs';
1920

2021
/**
2122
* getZoneUnPatchedApi
@@ -110,6 +111,11 @@ export class FastSvgComponent implements AfterViewInit, OnDestroy {
110111
width = input<string>('');
111112
height = input<string>('');
112113

114+
#url = toSignal(toObservable(this.name).pipe(switchMap((name) => {
115+
const url = this.registry.url(name);
116+
return typeof url === 'string' ? of(url) : url;
117+
})))
118+
113119
// When the browser loaded the svg resource we trigger the caching mechanism
114120
// re-fetch -> cache-hit -> get SVG -> cache in DOM
115121
loadedListener = () => {
@@ -142,7 +148,6 @@ export class FastSvgComponent implements AfterViewInit, OnDestroy {
142148
(onCleanup) => {
143149
const name = this.name();
144150

145-
untracked(() => {
146151
if (!name) {
147152
throw new Error('svg component needs a name to operate');
148153
}
@@ -153,32 +158,35 @@ export class FastSvgComponent implements AfterViewInit, OnDestroy {
153158
if (!this.registry.isSvgCached(name)) {
154159
/**
155160
CSR - Browser native lazy loading hack
156-
161+
157162
We use an img element here to leverage the browsers native features:
158163
- lazy loading (loading="lazy") to only load the svg that are actually visible
159164
- priority hints to down prioritize the fetch to avoid delaying the LCP
160-
165+
161166
While the SVG is loading we display a fallback SVG.
162167
After the image is loaded we remove it from the DOM. (IMG load event)
163168
When the new svg arrives we append it to the template.
164-
169+
165170
Note:
166171
- the image is styled with display none. this prevents any loading of the resource ever.
167172
on component bootstrap we decide what we want to do. when we remove display none it performs the browser native behavior
168-
- the image has 0 height and with and containment as well as contnet-visibility to reduce any performance impact
169-
170-
173+
- the image has 0 height and with and containment as well as content-visibility to reduce any performance impact
174+
175+
171176
Edge cases:
172177
- 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)
173178
- already loaded resources will get emitted from the cache immediately, even if loading is set to lazy :o
174179
- the image needs to have display other than none
175180
*/
176-
const i = this.getImg(this.registry.url(name));
177-
this.renderer.appendChild(this.element.nativeElement, i);
178-
179-
// get img
180-
img = elem.querySelector('img') as HTMLImageElement;
181-
addEventListener(img, 'load', this.loadedListener);
181+
const url = this.#url();
182+
if (url) {
183+
const i = this.getImg(url);
184+
this.renderer.appendChild(this.element.nativeElement, i);
185+
186+
// get img
187+
img = elem.querySelector('img') as HTMLImageElement;
188+
addEventListener(img, 'load', this.loadedListener);
189+
}
182190
}
183191

184192
// Listen to svg changes
@@ -225,7 +233,6 @@ export class FastSvgComponent implements AfterViewInit, OnDestroy {
225233
img.removeEventListener('load', this.loadedListener);
226234
}
227235
});
228-
});
229236
},
230237
{ injector: this.injector }
231238
);

packages/ngx-fast-lib/src/lib/token/svg-load.strategy.model.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@ import { Injectable } from '@angular/core';
33

44
@Injectable()
55
export abstract class SvgLoadStrategy {
6-
abstract load(url: string): Observable<string>;
6+
abstract load(url: string | Observable<string>): Observable<string>;
77
}
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
import { from, Observable } from 'rxjs';
1+
import { from, Observable, switchMap } from 'rxjs';
22
import { getZoneUnPatchedApi } from '../internal/get-zone-unpatched-api';
33
import { SvgLoadStrategy } from "./svg-load.strategy.model";
44

55
export class SvgLoadStrategyImpl extends SvgLoadStrategy {
66
fetch = getZoneUnPatchedApi('fetch', window as any);
77

8-
load(url: string): Observable<string> {
9-
return from(fetch(url).then((res) => (!res.ok ? '' : res.text())));
8+
load(url$: Observable<string>): Observable<string> {
9+
return url$.pipe(switchMap((url) =>from(fetch(url).then((res) => (!res.ok ? '' : res.text())))));
1010
}
1111
}
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
import { Observable } from 'rxjs';
2+
13
export type SvgOptions = {
2-
url: (name: string) => string;
4+
url: (name: string) => string | Observable<string>;
35
defaultSize?: string;
46
suspenseSvgString?: string;
57
};

0 commit comments

Comments
 (0)