@@ -5,15 +5,23 @@ import {
5
5
EmbeddedViewRef ,
6
6
Inject ,
7
7
Input ,
8
+ OnChanges ,
8
9
OnDestroy ,
9
10
OnInit ,
10
11
PLATFORM_ID ,
12
+ SimpleChanges ,
11
13
TemplateRef ,
12
14
ViewContainerRef ,
13
15
} from '@angular/core' ;
14
16
import { 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' ;
17
25
18
26
import {
19
27
ElementConfig ,
@@ -25,7 +33,7 @@ const LOG_PREFIX = '@angular-extensions/elements';
25
33
@Directive ( {
26
34
selector : '[axLazyElement]' ,
27
35
} )
28
- export class LazyElementDirective implements OnInit , OnDestroy {
36
+ export class LazyElementDirective implements OnChanges , OnInit , OnDestroy {
29
37
@Input ( 'axLazyElement' ) url : string ;
30
38
@Input ( 'axLazyElementLoadingTemplate' ) loadingTemplateRef : TemplateRef < any > ; // eslint-disable-line @angular-eslint/no-input-rename
31
39
@Input ( 'axLazyElementErrorTemplate' ) errorTemplateRef : TemplateRef < any > ; // eslint-disable-line @angular-eslint/no-input-rename
@@ -34,6 +42,7 @@ export class LazyElementDirective implements OnInit, OnDestroy {
34
42
35
43
private viewRef : EmbeddedViewRef < any > = null ;
36
44
private subscription = Subscription . EMPTY ;
45
+ private url$ = new BehaviorSubject < string | null > ( null ) ;
37
46
38
47
constructor (
39
48
@Inject ( PLATFORM_ID ) private platformId : string ,
@@ -44,6 +53,12 @@ export class LazyElementDirective implements OnInit, OnDestroy {
44
53
private cdr : ChangeDetectorRef
45
54
) { }
46
55
56
+ ngOnChanges ( changes : SimpleChanges ) : void {
57
+ if ( changes . url ) {
58
+ this . url$ . next ( this . url ) ;
59
+ }
60
+ }
61
+
47
62
ngOnInit ( ) {
48
63
// There's no sense to execute the below logic on the Node.js side since the JavaScript
49
64
// 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 {
53
68
return ;
54
69
}
55
70
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 {
56
87
const tpl = this . template as any ;
57
88
const elementTag = tpl . _declarationTContainer
58
89
? tpl . _declarationTContainer . tagName || tpl . _declarationTContainer . value
@@ -65,60 +96,58 @@ export class LazyElementDirective implements OnInit, OnDestroy {
65
96
const loadingComponent =
66
97
elementConfig . loadingComponent || options . loadingComponent ;
67
98
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
+ }
74
112
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 ) )
82
144
)
83
- ) ;
84
-
85
- this . subscription = loadElement$
86
- . pipe ( mergeMap ( ( ) => customElements . whenDefined ( elementTag ) ) )
87
145
. subscribe ( {
88
146
next : ( ) => {
89
147
this . vcr . clear ( ) ;
90
148
this . viewRef = this . vcr . createEmbeddedView ( this . template ) ;
91
149
this . cdr . markForCheck ( ) ;
92
150
} ,
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
- } ,
110
151
} ) ;
111
152
}
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
- }
124
153
}
0 commit comments