7
7
*/
8
8
9
9
import { CommonModule } from '@angular/common' ;
10
- import { Component , ContentChildren , Directive , NO_ERRORS_SCHEMA , QueryList , TemplateRef } from '@angular/core' ;
10
+ import { Component , ContentChildren , Directive , Injectable , NO_ERRORS_SCHEMA , OnDestroy , QueryList , TemplateRef } from '@angular/core' ;
11
11
import { ComponentFixture , TestBed , async } from '@angular/core/testing' ;
12
12
import { expect } from '@angular/platform-browser/testing/src/matchers' ;
13
13
@@ -26,11 +26,9 @@ export function main() {
26
26
27
27
beforeEach ( ( ) => {
28
28
TestBed . configureTestingModule ( {
29
- declarations : [
30
- TestComponent ,
31
- CaptureTplRefs ,
32
- ] ,
29
+ declarations : [ TestComponent , CaptureTplRefs , DestroyableCmpt ] ,
33
30
imports : [ CommonModule ] ,
31
+ providers : [ DestroyedSpyService ]
34
32
} ) ;
35
33
} ) ;
36
34
@@ -125,9 +123,105 @@ export function main() {
125
123
fixture . componentInstance . context = { shawshank : 'was here' } ;
126
124
detectChangesAndExpectText ( 'was here' ) ;
127
125
} ) ) ;
126
+
127
+ it ( 'should update but not destroy embedded view when context values change' , ( ) => {
128
+ const template =
129
+ `<ng-template let-foo="foo" #tpl><destroyable-cmpt></destroyable-cmpt>:{{foo}}</ng-template>` +
130
+ `<ng-template [ngTemplateOutlet]="tpl" [ngTemplateOutletContext]="{foo: value}"></ng-template>` ;
131
+
132
+ fixture = createTestComponent ( template ) ;
133
+ const spyService = fixture . debugElement . injector . get ( DestroyedSpyService ) ;
134
+
135
+ detectChangesAndExpectText ( 'Content to destroy:bar' ) ;
136
+ expect ( spyService . destroyed ) . toBeFalsy ( ) ;
137
+
138
+ fixture . componentInstance . value = 'baz' ;
139
+ detectChangesAndExpectText ( 'Content to destroy:baz' ) ;
140
+ expect ( spyService . destroyed ) . toBeFalsy ( ) ;
141
+ } ) ;
142
+
143
+ it ( 'should recreate embedded view when context shape changes' , ( ) => {
144
+ const template =
145
+ `<ng-template let-foo="foo" #tpl><destroyable-cmpt></destroyable-cmpt>:{{foo}}</ng-template>` +
146
+ `<ng-template [ngTemplateOutlet]="tpl" [ngTemplateOutletContext]="context"></ng-template>` ;
147
+
148
+ fixture = createTestComponent ( template ) ;
149
+ const spyService = fixture . debugElement . injector . get ( DestroyedSpyService ) ;
150
+
151
+ detectChangesAndExpectText ( 'Content to destroy:bar' ) ;
152
+ expect ( spyService . destroyed ) . toBeFalsy ( ) ;
153
+
154
+ fixture . componentInstance . context = { foo : 'baz' , other : true } ;
155
+ detectChangesAndExpectText ( 'Content to destroy:baz' ) ;
156
+ expect ( spyService . destroyed ) . toBeTruthy ( ) ;
157
+ } ) ;
158
+
159
+ it ( 'should destroy embedded view when context value changes and templateRef becomes undefined' ,
160
+ ( ) => {
161
+ const template =
162
+ `<ng-template let-foo="foo" #tpl><destroyable-cmpt></destroyable-cmpt>:{{foo}}</ng-template>` +
163
+ `<ng-template [ngTemplateOutlet]="value === 'bar' ? tpl : undefined" [ngTemplateOutletContext]="{foo: value}"></ng-template>` ;
164
+
165
+ fixture = createTestComponent ( template ) ;
166
+ const spyService = fixture . debugElement . injector . get ( DestroyedSpyService ) ;
167
+
168
+ detectChangesAndExpectText ( 'Content to destroy:bar' ) ;
169
+ expect ( spyService . destroyed ) . toBeFalsy ( ) ;
170
+
171
+ fixture . componentInstance . value = 'baz' ;
172
+ detectChangesAndExpectText ( '' ) ;
173
+ expect ( spyService . destroyed ) . toBeTruthy ( ) ;
174
+ } ) ;
175
+
176
+ it ( 'should not try to update null / undefined context when context changes but template stays the same' ,
177
+ ( ) => {
178
+ const template = `<ng-template let-foo="foo" #tpl>{{foo}}</ng-template>` +
179
+ `<ng-template [ngTemplateOutlet]="tpl" [ngTemplateOutletContext]="value === 'bar' ? null : undefined"></ng-template>` ;
180
+
181
+ fixture = createTestComponent ( template ) ;
182
+ detectChangesAndExpectText ( '' ) ;
183
+
184
+ fixture . componentInstance . value = 'baz' ;
185
+ detectChangesAndExpectText ( '' ) ;
186
+ } ) ;
187
+
188
+ it ( 'should not try to update null / undefined context when template changes' , ( ) => {
189
+ const template = `<ng-template let-foo="foo" #tpl1>{{foo}}</ng-template>` +
190
+ `<ng-template let-foo="foo" #tpl2>{{foo}}</ng-template>` +
191
+ `<ng-template [ngTemplateOutlet]="value === 'bar' ? tpl1 : tpl2" [ngTemplateOutletContext]="value === 'bar' ? null : undefined"></ng-template>` ;
192
+
193
+ fixture = createTestComponent ( template ) ;
194
+ detectChangesAndExpectText ( '' ) ;
195
+
196
+ fixture . componentInstance . value = 'baz' ;
197
+ detectChangesAndExpectText ( '' ) ;
198
+ } ) ;
199
+
200
+ it ( 'should not try to update context on undefined view' , ( ) => {
201
+ const template = `<ng-template let-foo="foo" #tpl>{{foo}}</ng-template>` +
202
+ `<ng-template [ngTemplateOutlet]="value === 'bar' ? null : undefined" [ngTemplateOutletContext]="{foo: value}"></ng-template>` ;
203
+
204
+ fixture = createTestComponent ( template ) ;
205
+ detectChangesAndExpectText ( '' ) ;
206
+
207
+ fixture . componentInstance . value = 'baz' ;
208
+ detectChangesAndExpectText ( '' ) ;
209
+ } ) ;
128
210
} ) ;
129
211
}
130
212
213
+ @Injectable ( )
214
+ class DestroyedSpyService {
215
+ destroyed = false ;
216
+ }
217
+
218
+ @Component ( { selector : 'destroyable-cmpt' , template : 'Content to destroy' } )
219
+ class DestroyableCmpt implements OnDestroy {
220
+ constructor ( private _spyService : DestroyedSpyService ) { }
221
+
222
+ ngOnDestroy ( ) : void { this . _spyService . destroyed = true ; }
223
+ }
224
+
131
225
@Directive ( { selector : 'tpl-refs' , exportAs : 'tplRefs' } )
132
226
class CaptureTplRefs {
133
227
@ContentChildren ( TemplateRef ) tplRefs : QueryList < TemplateRef < any > > ;
@@ -137,6 +231,7 @@ class CaptureTplRefs {
137
231
class TestComponent {
138
232
currentTplRef : TemplateRef < any > ;
139
233
context : any = { foo : 'bar' } ;
234
+ value = 'bar' ;
140
235
}
141
236
142
237
function createTestComponent ( template : string ) : ComponentFixture < TestComponent > {
0 commit comments