@@ -5,13 +5,15 @@ import {
55  ChangeDetectionStrategy , 
66  Component , 
77  ElementRef , 
8-   Input , 
8+   Injector , 
99  OnDestroy , 
1010  PLATFORM_ID , 
1111  Renderer2 , 
12+   effect , 
1213  inject , 
14+   input , 
15+   untracked , 
1316}  from  '@angular/core' ; 
14- import  {  Subscription  }  from  'rxjs' ; 
1517import  {  getZoneUnPatchedApi  }  from  './internal/get-zone-unpatched-api' ; 
1618import  {  SvgRegistry  }  from  './svg-registry.service' ; 
1719
@@ -95,119 +97,141 @@ function createGetImgFn(renderer: Renderer2): (src: string) => HTMLElement {
9597  changeDetection : ChangeDetectionStrategy . OnPush , 
9698} ) 
9799export  class  FastSvgComponent  implements  AfterViewInit ,  OnDestroy  { 
100+   private  readonly  injector  =  inject ( Injector ) ; 
98101  private  readonly  platform  =  inject ( PLATFORM_ID ) ; 
99102  private  readonly  renderer  =  inject ( Renderer2 ) ; 
100103  private  readonly  registry  =  inject ( SvgRegistry ) ; 
101104  private  readonly  element  =  inject < ElementRef < HTMLElement > > ( ElementRef ) ; 
102105
103-   private  readonly  sub  =  new  Subscription ( ) ; 
104106  private  readonly  getImg  =  createGetImgFn ( this . renderer ) ; 
105107
106-   @ Input ( )   name  =  '' ; 
107-   @ Input ( )   size :  string   =   this . registry . defaultSize ; 
108-   @ Input ( )   width  =  '' ; 
109-   @ Input ( )   height  =  '' ; 
108+   name  =  input < string > ( '' ) ; 
109+   size   =   input < string > ( this . registry . defaultSize ) ; 
110+   width  =  input < string > ( '' ) ; 
111+   height  =  input < string > ( '' ) ; 
110112
111113  // When the browser loaded the svg resource we trigger the caching mechanism 
112114  // re-fetch -> cache-hit -> get SVG -> cache in DOM 
113115  loadedListener  =  ( )  =>  { 
114-     this . registry . fetchSvg ( this . name ) ; 
116+     this . registry . fetchSvg ( this . name ( ) ) ; 
115117  } ; 
116118
117119  ngAfterViewInit ( )  { 
118-     if  ( ! this . name )  { 
119-       throw  new  Error ( 'svg component needs a name to operate' ) ; 
120-     } 
121- 
122120    // Setup view refs and init them 
123121    const  elem  =  this . element . nativeElement ; 
124122
125123    const  svg  =  elem . querySelector ( 'svg' )  as  SVGElement ; 
126-     // apply size 
127-     if  ( this . size  &&  svg )  { 
128-       // We apply fixed dimensions 
129-       // Additionally to SEO rules, to avoid any scroll flicker caused by `content-visibility:auto` defined in component styles 
130-       svg . setAttribute ( 'width' ,  this . width  ||  this . size ) ; 
131-       svg . setAttribute ( 'height' ,  this . height  ||  this . width  ||  this . size ) ; 
132-     } 
133- 
134-     let  img : HTMLImageElement  |  null  =  null ; 
135- 
136-     // if svg is not in cache we append 
137-     if  ( ! this . registry . isSvgCached ( this . name ) )  { 
138-       /** 
139-        CSR - Browser native lazy loading hack 
140- 
141-        We use an img element here to leverage the browsers native features: 
142-        - lazy loading (loading="lazy") to only load the svg that are actually visible 
143-        - priority hints to down prioritize the fetch to avoid delaying the LCP 
144- 
145-        While the SVG is loading we display a fallback SVG. 
146-        After the image is loaded we remove it from the DOM. (IMG load event) 
147-        When the new svg arrives we append it to the template. 
148- 
149-        Note: 
150-        - the image is styled with display none. this prevents any loading of the resource ever. 
151-        on component bootstrap we decide what we want to do. when we remove display none it performs the browser native behavior 
152-        - the image has 0 height and with and containment as well as contnet-visibility to recuce any performance impact 
153- 
154- 
155-        Edge cases: 
156-        - 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) 
157-        - already loaded resources will get emitted from the cache immediately, even if loading is set to lazy :o 
158-        - the image needs to have display other than none 
159- 
160-        */ 
161-       const  i  =  this . getImg ( this . registry . url ( this . name ) ) ; 
162-       this . renderer . appendChild ( this . element . nativeElement ,  i ) ; 
163- 
164-       // get img 
165-       img  =  elem . querySelector ( 'img' )  as  HTMLImageElement ; 
166-       addEventListener ( img ,  'load' ,  this . loadedListener ) ; 
167-     } 
168- 
169-     // Listen to svg changes 
170-     // This potentially could already receive the svg from the cache and drop the img from the DOM before it gets activated for lazy loading. 
171-     // NOTICE: 
172-     // If the svg is already cached the following code will execute synchronously. This gives us the chance to add 
173-     this . sub . add ( 
174-       this . registry . svgCache$ ( this . name ) . subscribe ( ( cache )  =>  { 
175-         // The first child is the `use` tag. The value of href gets displayed as SVG 
176-         svg . children [ 0 ] . setAttribute ( 'href' ,  cache . name ) ; 
177-         svg . setAttribute ( 'viewBox' ,  cache . viewBox ) ; 
178- 
179-         // early exvit no image 
180-         if  ( ! img )  return ; 
181- 
182-         // If the img is present 
183-         // and the name in included in the href (svg is fully loaded, not only the suspense svg) 
184-         // Remove the element from the DOM as it is no longer needed 
185-         if  ( cache . name . includes ( this . name ) )  { 
186-           img . removeEventListener ( 'load' ,  this . loadedListener ) ; 
187-           // removeEventListener.bind(img, 'load', this.loadedListener); 
188-           img . remove ( ) ; 
124+ 
125+     effect ( 
126+       ( )  =>  { 
127+         // apply size 
128+         if  ( this . size ( )  &&  svg )  { 
129+           // We apply fixed dimensions 
130+           // Additionally to SEO rules, to avoid any scroll flicker caused by `content-visibility:auto` defined in component styles 
131+           svg . setAttribute ( 'width' ,  this . width ( )  ||  this . size ( ) ) ; 
132+           svg . setAttribute ( 
133+             'height' , 
134+             this . height ( )  ||  this . width ( )  ||  this . size ( ) 
135+           ) ; 
189136        } 
190-       } ) 
137+       } , 
138+       {  injector : this . injector  } 
191139    ) ; 
192140
193-     // SSR 
194-     if  ( isPlatformServer ( this . platform ) )  { 
195-       // if SSR load svgs on server => ends up in DOM cache and ships to the client 
196-       this . registry . fetchSvg ( this . name ) ; 
197-     } 
198-     // CSR 
199-     else  { 
200-       // Activate the lazy loading hack 
201-       // Loading is triggered in the template over loading="lazy" and onload 
202-       // Than the same image is fetched over fromFetch and rendered as SVG. (This will result in a cache hit for this svg) 
203-       // 
204-       // If the img is present activate it 
205-       img  &&  img . style . setProperty ( 'display' ,  'block' ) ; 
206-     } 
141+     effect ( 
142+       ( onCleanup )  =>  { 
143+         const  name  =  this . name ( ) ; 
144+ 
145+         untracked ( ( )  =>  { 
146+           if  ( ! name )  { 
147+             throw  new  Error ( 'svg component needs a name to operate' ) ; 
148+           } 
149+ 
150+           let  img : HTMLImageElement  |  null  =  null ; 
151+ 
152+           // if svg is not in cache we append 
153+           if  ( ! this . registry . isSvgCached ( name ) )  { 
154+             /** 
155+               CSR - Browser native lazy loading hack 
156+          
157+               We use an img element here to leverage the browsers native features: 
158+               - lazy loading (loading="lazy") to only load the svg that are actually visible 
159+               - priority hints to down prioritize the fetch to avoid delaying the LCP 
160+          
161+               While the SVG is loading we display a fallback SVG. 
162+               After the image is loaded we remove it from the DOM. (IMG load event) 
163+               When the new svg arrives we append it to the template. 
164+          
165+               Note: 
166+               - the image is styled with display none. this prevents any loading of the resource ever. 
167+               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+          
171+               Edge cases: 
172+               - 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) 
173+               - already loaded resources will get emitted from the cache immediately, even if loading is set to lazy :o 
174+               - the image needs to have display other than none 
175+             */ 
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 ) ; 
182+           } 
183+ 
184+           // Listen to svg changes 
185+           // This potentially could already receive the svg from the cache and drop the img from the DOM before it gets activated for lazy loading. 
186+           // NOTICE: 
187+           // If the svg is already cached the following code will execute synchronously. This gives us the chance to add 
188+           const  sub  =  this . registry . svgCache$ ( name ) . subscribe ( ( cache )  =>  { 
189+             // The first child is the `use` tag. The value of href gets displayed as SVG 
190+             svg . children [ 0 ] . setAttribute ( 'href' ,  cache . name ) ; 
191+             svg . setAttribute ( 'viewBox' ,  cache . viewBox ) ; 
192+ 
193+             // early exit no image 
194+             if  ( ! img )  return ; 
195+ 
196+             // If the img is present 
197+             // and the name in included in the href (svg is fully loaded, not only the suspense svg) 
198+             // Remove the element from the DOM as it is no longer needed 
199+             if  ( cache . name . includes ( name ) )  { 
200+               img . removeEventListener ( 'load' ,  this . loadedListener ) ; 
201+               // removeEventListener.bind(img, 'load', this.loadedListener); 
202+               img . remove ( ) ; 
203+             } 
204+           } ) ; 
205+ 
206+           // SSR 
207+           if  ( isPlatformServer ( this . platform ) )  { 
208+             // if SSR load svgs on server => ends up in DOM cache and ships to the client 
209+             this . registry . fetchSvg ( name ) ; 
210+           } 
211+           // CSR 
212+           else  { 
213+             // Activate the lazy loading hack 
214+             // Loading is triggered in the template over loading="lazy" and onload 
215+             // Than the same image is fetched over fromFetch and rendered as SVG. (This will result in a cache hit for this svg) 
216+             // 
217+             // If the img is present activate it 
218+             img  &&  img . style . setProperty ( 'display' ,  'block' ) ; 
219+           } 
220+ 
221+           onCleanup ( ( )  =>  { 
222+             sub . unsubscribe ( ) ; 
223+ 
224+             if  ( img )  { 
225+               img . removeEventListener ( 'load' ,  this . loadedListener ) ; 
226+             } 
227+           } ) ; 
228+         } ) ; 
229+       } , 
230+       {  injector : this . injector  } 
231+     ) ; 
207232  } 
208233
209234  ngOnDestroy ( )  { 
210-     this . sub . unsubscribe ( ) ; 
211235    this . element . nativeElement 
212236      . querySelector ( 'img' ) 
213237      ?. removeEventListener ( 'load' ,  this . loadedListener ) ; 
0 commit comments