@@ -5,15 +5,23 @@ import {
55 EmbeddedViewRef ,
66 Inject ,
77 Input ,
8+ OnChanges ,
89 OnDestroy ,
910 OnInit ,
1011 PLATFORM_ID ,
12+ SimpleChanges ,
1113 TemplateRef ,
1214 ViewContainerRef ,
1315} from '@angular/core' ;
1416import { isPlatformServer } from '@angular/common' ;
15- import { from , Subscription } from 'rxjs' ;
16- import { mergeMap } from 'rxjs/operators' ;
17+ import {
18+ animationFrameScheduler ,
19+ BehaviorSubject ,
20+ EMPTY ,
21+ from ,
22+ Subscription ,
23+ } from 'rxjs' ;
24+ import { catchError , debounceTime , mergeMap , switchMap } from 'rxjs/operators' ;
1725
1826import {
1927 ElementConfig ,
@@ -25,7 +33,7 @@ const LOG_PREFIX = '@angular-extensions/elements';
2533@Directive ( {
2634 selector : '[axLazyElement]' ,
2735} )
28- export class LazyElementDirective implements OnInit , OnDestroy {
36+ export class LazyElementDirective implements OnChanges , OnInit , OnDestroy {
2937 @Input ( 'axLazyElement' ) url : string ;
3038 @Input ( 'axLazyElementLoadingTemplate' ) loadingTemplateRef : TemplateRef < any > ; // eslint-disable-line @angular-eslint/no-input-rename
3139 @Input ( 'axLazyElementErrorTemplate' ) errorTemplateRef : TemplateRef < any > ; // eslint-disable-line @angular-eslint/no-input-rename
@@ -34,6 +42,7 @@ export class LazyElementDirective implements OnInit, OnDestroy {
3442
3543 private viewRef : EmbeddedViewRef < any > = null ;
3644 private subscription = Subscription . EMPTY ;
45+ private url$ = new BehaviorSubject < string | null > ( null ) ;
3746
3847 constructor (
3948 @Inject ( PLATFORM_ID ) private platformId : string ,
@@ -44,6 +53,12 @@ export class LazyElementDirective implements OnInit, OnDestroy {
4453 private cdr : ChangeDetectorRef
4554 ) { }
4655
56+ ngOnChanges ( changes : SimpleChanges ) : void {
57+ if ( changes . url ) {
58+ this . url$ . next ( this . url ) ;
59+ }
60+ }
61+
4762 ngOnInit ( ) {
4863 // There's no sense to execute the below logic on the Node.js side since the JavaScript
4964 // will not be loaded on the server-side (Angular will only append the script to body).
@@ -53,6 +68,22 @@ export class LazyElementDirective implements OnInit, OnDestroy {
5368 return ;
5469 }
5570
71+ this . setupUrlListener ( ) ;
72+ }
73+
74+ ngOnDestroy ( ) : void {
75+ this . subscription . unsubscribe ( ) ;
76+ }
77+
78+ destroyEmbeddedView ( ) {
79+ if ( this . viewRef && ! this . viewRef . destroyed ) {
80+ this . viewRef . detach ( ) ;
81+ this . viewRef . destroy ( ) ;
82+ this . viewRef = null ;
83+ }
84+ }
85+
86+ private setupUrlListener ( ) : void {
5687 const tpl = this . template as any ;
5788 const elementTag = tpl . _declarationTContainer
5889 ? tpl . _declarationTContainer . tagName || tpl . _declarationTContainer . value
@@ -65,60 +96,58 @@ export class LazyElementDirective implements OnInit, OnDestroy {
6596 const loadingComponent =
6697 elementConfig . loadingComponent || options . loadingComponent ;
6798
68- if ( this . loadingTemplateRef ) {
69- this . vcr . createEmbeddedView ( this . loadingTemplateRef ) ;
70- } else if ( loadingComponent ) {
71- const factory = this . cfr . resolveComponentFactory ( loadingComponent ) ;
72- this . vcr . createComponent ( factory ) ;
73- }
99+ this . subscription = this . url$
100+ . pipe (
101+ // This is used to coalesce changes since the `url$` subject might emit multiple values initially, e.g.
102+ // `null` (initial value) and the url itself (when the `url` binding is provided).
103+ // The `animationFrameScheduler` is used to prevent the frame drop.
104+ debounceTime ( 0 , animationFrameScheduler ) ,
105+ switchMap ( ( url ) => {
106+ if ( this . loadingTemplateRef ) {
107+ this . vcr . createEmbeddedView ( this . loadingTemplateRef ) ;
108+ } else if ( loadingComponent ) {
109+ const factory = this . cfr . resolveComponentFactory ( loadingComponent ) ;
110+ this . vcr . createComponent ( factory ) ;
111+ }
74112
75- const loadElement$ = from (
76- this . elementsLoaderService . loadElement (
77- this . url ,
78- elementTag ,
79- this . isModule ,
80- this . importMap ,
81- elementConfig ?. hooks
113+ return from (
114+ this . elementsLoaderService . loadElement (
115+ url ,
116+ elementTag ,
117+ this . isModule ,
118+ this . importMap ,
119+ elementConfig ?. hooks
120+ )
121+ ) . pipe (
122+ catchError ( ( ) => {
123+ this . vcr . clear ( ) ;
124+ const errorComponent =
125+ elementConfig . errorComponent || options . errorComponent ;
126+ if ( this . errorTemplateRef ) {
127+ this . vcr . createEmbeddedView ( this . errorTemplateRef ) ;
128+ this . cdr . markForCheck ( ) ;
129+ } else if ( errorComponent ) {
130+ const factory =
131+ this . cfr . resolveComponentFactory ( errorComponent ) ;
132+ this . vcr . createComponent ( factory ) ;
133+ this . cdr . markForCheck ( ) ;
134+ } else if ( ngDevMode ) {
135+ console . error (
136+ `${ LOG_PREFIX } - Loading of element <${ elementTag } > failed, please provide <ng-template #error>Loading failed...</ng-template> and reference it in *axLazyElement="errorTemplate: error" to display customized error message in place of element`
137+ ) ;
138+ }
139+ return EMPTY ;
140+ } )
141+ ) ;
142+ } ) ,
143+ mergeMap ( ( ) => customElements . whenDefined ( elementTag ) )
82144 )
83- ) ;
84-
85- this . subscription = loadElement$
86- . pipe ( mergeMap ( ( ) => customElements . whenDefined ( elementTag ) ) )
87145 . subscribe ( {
88146 next : ( ) => {
89147 this . vcr . clear ( ) ;
90148 this . viewRef = this . vcr . createEmbeddedView ( this . template ) ;
91149 this . cdr . markForCheck ( ) ;
92150 } ,
93- error : ( ) => {
94- this . vcr . clear ( ) ;
95- const errorComponent =
96- elementConfig . errorComponent || options . errorComponent ;
97- if ( this . errorTemplateRef ) {
98- this . vcr . createEmbeddedView ( this . errorTemplateRef ) ;
99- this . cdr . markForCheck ( ) ;
100- } else if ( errorComponent ) {
101- const factory = this . cfr . resolveComponentFactory ( errorComponent ) ;
102- this . vcr . createComponent ( factory ) ;
103- this . cdr . markForCheck ( ) ;
104- } else if ( ngDevMode ) {
105- console . error (
106- `${ LOG_PREFIX } - Loading of element <${ elementTag } > failed, please provide <ng-template #error>Loading failed...</ng-template> and reference it in *axLazyElement="errorTemplate: error" to display customized error message in place of element`
107- ) ;
108- }
109- } ,
110151 } ) ;
111152 }
112-
113- ngOnDestroy ( ) : void {
114- this . subscription . unsubscribe ( ) ;
115- }
116-
117- destroyEmbeddedView ( ) {
118- if ( this . viewRef && ! this . viewRef . destroyed ) {
119- this . viewRef . detach ( ) ;
120- this . viewRef . destroy ( ) ;
121- this . viewRef = null ;
122- }
123- }
124153}
0 commit comments