@@ -12,8 +12,14 @@ import {
12
12
ViewContainerRef ,
13
13
} from '@angular/core' ;
14
14
import { isPlatformServer } from '@angular/common' ;
15
- import { from , Subscription } from 'rxjs' ;
16
- import { mergeMap } from 'rxjs/operators' ;
15
+ import {
16
+ animationFrameScheduler ,
17
+ BehaviorSubject ,
18
+ EMPTY ,
19
+ from ,
20
+ Subscription ,
21
+ } from 'rxjs' ;
22
+ import { catchError , debounceTime , mergeMap , switchMap } from 'rxjs/operators' ;
17
23
18
24
import {
19
25
ElementConfig ,
@@ -26,14 +32,19 @@ const LOG_PREFIX = '@angular-extensions/elements';
26
32
selector : '[axLazyElement]' ,
27
33
} )
28
34
export class LazyElementDirective implements OnInit , OnDestroy {
29
- @Input ( 'axLazyElement' ) url : string ;
35
+ @Input ( 'axLazyElement' )
36
+ set url ( url : string ) {
37
+ this . url$ . next ( url ) ;
38
+ }
39
+
30
40
@Input ( 'axLazyElementLoadingTemplate' ) loadingTemplateRef : TemplateRef < any > ; // eslint-disable-line @angular-eslint/no-input-rename
31
41
@Input ( 'axLazyElementErrorTemplate' ) errorTemplateRef : TemplateRef < any > ; // eslint-disable-line @angular-eslint/no-input-rename
32
42
@Input ( 'axLazyElementModule' ) isModule : boolean | undefined ; // eslint-disable-line @angular-eslint/no-input-rename
33
43
@Input ( 'axLazyElementImportMap' ) importMap : boolean | undefined ; // eslint-disable-line @angular-eslint/no-input-rename
34
44
35
45
private viewRef : EmbeddedViewRef < any > = null ;
36
46
private subscription = Subscription . EMPTY ;
47
+ private url$ = new BehaviorSubject < string | null > ( null ) ;
37
48
38
49
constructor (
39
50
@Inject ( PLATFORM_ID ) private platformId : string ,
@@ -53,6 +64,22 @@ export class LazyElementDirective implements OnInit, OnDestroy {
53
64
return ;
54
65
}
55
66
67
+ this . setupUrlListener ( ) ;
68
+ }
69
+
70
+ ngOnDestroy ( ) : void {
71
+ this . subscription . unsubscribe ( ) ;
72
+ }
73
+
74
+ destroyEmbeddedView ( ) {
75
+ if ( this . viewRef && ! this . viewRef . destroyed ) {
76
+ this . viewRef . detach ( ) ;
77
+ this . viewRef . destroy ( ) ;
78
+ this . viewRef = null ;
79
+ }
80
+ }
81
+
82
+ private setupUrlListener ( ) : void {
56
83
const tpl = this . template as any ;
57
84
const elementTag = tpl . _declarationTContainer
58
85
? tpl . _declarationTContainer . tagName || tpl . _declarationTContainer . value
@@ -65,60 +92,58 @@ export class LazyElementDirective implements OnInit, OnDestroy {
65
92
const loadingComponent =
66
93
elementConfig . loadingComponent || options . loadingComponent ;
67
94
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
- }
95
+ this . subscription = this . url$
96
+ . pipe (
97
+ // This is used to coalesce changes since the `url$` subject might emit multiple values initially, e.g.
98
+ // `null` (initial value) and the url itself (when the `url` binding is provided).
99
+ // The `animationFrameScheduler` is used to prevent the frame drop.
100
+ debounceTime ( 0 , animationFrameScheduler ) ,
101
+ switchMap ( ( url ) => {
102
+ if ( this . loadingTemplateRef ) {
103
+ this . vcr . createEmbeddedView ( this . loadingTemplateRef ) ;
104
+ } else if ( loadingComponent ) {
105
+ const factory = this . cfr . resolveComponentFactory ( loadingComponent ) ;
106
+ this . vcr . createComponent ( factory ) ;
107
+ }
74
108
75
- const loadElement$ = from (
76
- this . elementsLoaderService . loadElement (
77
- this . url ,
78
- elementTag ,
79
- this . isModule ,
80
- this . importMap ,
81
- elementConfig ?. hooks
109
+ return from (
110
+ this . elementsLoaderService . loadElement (
111
+ url ,
112
+ elementTag ,
113
+ this . isModule ,
114
+ this . importMap ,
115
+ elementConfig ?. hooks
116
+ )
117
+ ) . pipe (
118
+ catchError ( ( ) => {
119
+ this . vcr . clear ( ) ;
120
+ const errorComponent =
121
+ elementConfig . errorComponent || options . errorComponent ;
122
+ if ( this . errorTemplateRef ) {
123
+ this . vcr . createEmbeddedView ( this . errorTemplateRef ) ;
124
+ this . cdr . markForCheck ( ) ;
125
+ } else if ( errorComponent ) {
126
+ const factory =
127
+ this . cfr . resolveComponentFactory ( errorComponent ) ;
128
+ this . vcr . createComponent ( factory ) ;
129
+ this . cdr . markForCheck ( ) ;
130
+ } else if ( ngDevMode ) {
131
+ console . error (
132
+ `${ 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`
133
+ ) ;
134
+ }
135
+ return EMPTY ;
136
+ } )
137
+ ) ;
138
+ } ) ,
139
+ mergeMap ( ( ) => customElements . whenDefined ( elementTag ) )
82
140
)
83
- ) ;
84
-
85
- this . subscription = loadElement$
86
- . pipe ( mergeMap ( ( ) => customElements . whenDefined ( elementTag ) ) )
87
141
. subscribe ( {
88
142
next : ( ) => {
89
143
this . vcr . clear ( ) ;
90
144
this . viewRef = this . vcr . createEmbeddedView ( this . template ) ;
91
145
this . cdr . markForCheck ( ) ;
92
146
} ,
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
147
} ) ;
111
148
}
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
149
}
0 commit comments