6
6
* found in the LICENSE file at https://angular.io/license
7
7
*/
8
8
9
- import { ApplicationRef , Component , Inject , Injector , Input , NgModule , NgZone , OnChanges , OnDestroy , StaticProvider , ViewRef , destroyPlatform } from '@angular/core' ;
9
+ import { AfterContentChecked , AfterContentInit , AfterViewChecked , AfterViewInit , ApplicationRef , Component , DoCheck , Inject , Injector , Input , NgModule , NgZone , OnChanges , OnDestroy , OnInit , StaticProvider , ViewRef , destroyPlatform } from '@angular/core' ;
10
10
import { async , fakeAsync , tick } from '@angular/core/testing' ;
11
11
import { BrowserModule } from '@angular/platform-browser' ;
12
12
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic' ;
@@ -16,7 +16,7 @@ import {$ROOT_SCOPE, INJECTOR_KEY, LAZY_MODULE_REF} from '@angular/upgrade/src/c
16
16
import { LazyModuleRef } from '@angular/upgrade/src/common/util' ;
17
17
import { downgradeComponent , downgradeModule } from '@angular/upgrade/static' ;
18
18
19
- import { html } from '../test_helpers' ;
19
+ import { html , multiTrim } from '../test_helpers' ;
20
20
21
21
22
22
export function main ( ) {
@@ -306,6 +306,162 @@ export function main() {
306
306
} ) ;
307
307
} ) ) ;
308
308
309
+ it ( 'should run the lifecycle hooks in the correct order' , async ( ( ) => {
310
+ const logs : string [ ] = [ ] ;
311
+ let rootScope : angular . IRootScopeService ;
312
+
313
+ @Component ( {
314
+ selector : 'ng2' ,
315
+ template : `
316
+ {{ value }}
317
+ <button (click)="value = 'qux'"></button>
318
+ <ng-content></ng-content>
319
+ `
320
+ } )
321
+ class Ng2Component implements AfterContentChecked ,
322
+ AfterContentInit , AfterViewChecked , AfterViewInit , DoCheck , OnChanges , OnDestroy ,
323
+ OnInit {
324
+ @Input ( ) value = 'foo' ;
325
+
326
+ ngAfterContentChecked ( ) { this . log ( 'AfterContentChecked' ) ; }
327
+ ngAfterContentInit ( ) { this . log ( 'AfterContentInit' ) ; }
328
+ ngAfterViewChecked ( ) { this . log ( 'AfterViewChecked' ) ; }
329
+ ngAfterViewInit ( ) { this . log ( 'AfterViewInit' ) ; }
330
+ ngDoCheck ( ) { this . log ( 'DoCheck' ) ; }
331
+ ngOnChanges ( ) { this . log ( 'OnChanges' ) ; }
332
+ ngOnDestroy ( ) { this . log ( 'OnDestroy' ) ; }
333
+ ngOnInit ( ) { this . log ( 'OnInit' ) ; }
334
+
335
+ private log ( hook : string ) { logs . push ( `${ hook } (${ this . value } )` ) ; }
336
+ }
337
+
338
+ @NgModule ( {
339
+ declarations : [ Ng2Component ] ,
340
+ entryComponents : [ Ng2Component ] ,
341
+ imports : [ BrowserModule ] ,
342
+ } )
343
+ class Ng2Module {
344
+ ngDoBootstrap ( ) { }
345
+ }
346
+
347
+ const bootstrapFn = ( extraProviders : StaticProvider [ ] ) =>
348
+ platformBrowserDynamic ( extraProviders ) . bootstrapModule ( Ng2Module ) ;
349
+ const lazyModuleName = downgradeModule < Ng2Module > ( bootstrapFn ) ;
350
+ const ng1Module =
351
+ angular . module ( 'ng1' , [ lazyModuleName ] )
352
+ . directive ( 'ng2' , downgradeComponent ( { component : Ng2Component , propagateDigest} ) )
353
+ . run ( ( $rootScope : angular . IRootScopeService ) => {
354
+ rootScope = $rootScope ;
355
+ rootScope . value = 'bar' ;
356
+ } ) ;
357
+
358
+ const element =
359
+ html ( '<div><ng2 value="{{ value }}" ng-if="!hideNg2">Content</ng2></div>' ) ;
360
+ angular . bootstrap ( element , [ ng1Module . name ] ) ;
361
+
362
+ setTimeout ( ( ) => { // Wait for the module to be bootstrapped.
363
+ setTimeout ( ( ) => { // Wait for `$evalAsync()` to propagate inputs.
364
+ const button = element . querySelector ( 'button' ) ! ;
365
+
366
+ // Once initialized.
367
+ expect ( multiTrim ( element . textContent ) ) . toBe ( 'bar Content' ) ;
368
+ expect ( logs ) . toEqual ( [
369
+ // `ngOnChanges()` call triggered directly through the `inputChanges` $watcher.
370
+ 'OnChanges(bar)' ,
371
+ // Initial CD triggered directly through the `detectChanges()` or `inputChanges`
372
+ // $watcher (for `propagateDigest` true/false respectively).
373
+ 'OnInit(bar)' ,
374
+ 'DoCheck(bar)' ,
375
+ 'AfterContentInit(bar)' ,
376
+ 'AfterContentChecked(bar)' ,
377
+ 'AfterViewInit(bar)' ,
378
+ 'AfterViewChecked(bar)' ,
379
+ ...( propagateDigest ?
380
+ [
381
+ // CD triggered directly through the `detectChanges()` $watcher (2nd
382
+ // $digest).
383
+ 'DoCheck(bar)' ,
384
+ 'AfterContentChecked(bar)' ,
385
+ 'AfterViewChecked(bar)' ,
386
+ ] :
387
+ [ ] ) ,
388
+ // CD triggered due to entering/leaving the NgZone (in `downgradeFn()`).
389
+ 'DoCheck(bar)' ,
390
+ 'AfterContentChecked(bar)' ,
391
+ 'AfterViewChecked(bar)' ,
392
+ ] ) ;
393
+ logs . length = 0 ;
394
+
395
+ // Change inputs and run `$digest`.
396
+ rootScope . $apply ( 'value = "baz"' ) ;
397
+ expect ( multiTrim ( element . textContent ) ) . toBe ( 'baz Content' ) ;
398
+ expect ( logs ) . toEqual ( [
399
+ // `ngOnChanges()` call triggered directly through the `inputChanges` $watcher.
400
+ 'OnChanges(baz)' ,
401
+ // `propagateDigest: true` (3 CD runs):
402
+ // - CD triggered due to entering/leaving the NgZone (in `inputChanges`
403
+ // $watcher).
404
+ // - CD triggered directly through the `detectChanges()` $watcher.
405
+ // - CD triggered due to entering/leaving the NgZone (in `detectChanges`
406
+ // $watcher).
407
+ // `propagateDigest: false` (2 CD runs):
408
+ // - CD triggered directly through the `inputChanges` $watcher.
409
+ // - CD triggered due to entering/leaving the NgZone (in `inputChanges`
410
+ // $watcher).
411
+ 'DoCheck(baz)' ,
412
+ 'AfterContentChecked(baz)' ,
413
+ 'AfterViewChecked(baz)' ,
414
+ 'DoCheck(baz)' ,
415
+ 'AfterContentChecked(baz)' ,
416
+ 'AfterViewChecked(baz)' ,
417
+ ...( propagateDigest ?
418
+ [
419
+ 'DoCheck(baz)' ,
420
+ 'AfterContentChecked(baz)' ,
421
+ 'AfterViewChecked(baz)' ,
422
+ ] :
423
+ [ ] ) ,
424
+ ] ) ;
425
+ logs . length = 0 ;
426
+
427
+ // Run `$digest` (without changing inputs).
428
+ rootScope . $digest ( ) ;
429
+ expect ( multiTrim ( element . textContent ) ) . toBe ( 'baz Content' ) ;
430
+ expect ( logs ) . toEqual (
431
+ propagateDigest ?
432
+ [
433
+ // CD triggered directly through the `detectChanges()` $watcher.
434
+ 'DoCheck(baz)' ,
435
+ 'AfterContentChecked(baz)' ,
436
+ 'AfterViewChecked(baz)' ,
437
+ // CD triggered due to entering/leaving the NgZone (in the above $watcher).
438
+ 'DoCheck(baz)' ,
439
+ 'AfterContentChecked(baz)' ,
440
+ 'AfterViewChecked(baz)' ,
441
+ ] :
442
+ [ ] ) ;
443
+ logs . length = 0 ;
444
+
445
+ // Trigger change detection (without changing inputs).
446
+ button . click ( ) ;
447
+ expect ( multiTrim ( element . textContent ) ) . toBe ( 'qux Content' ) ;
448
+ expect ( logs ) . toEqual ( [
449
+ 'DoCheck(qux)' ,
450
+ 'AfterContentChecked(qux)' ,
451
+ 'AfterViewChecked(qux)' ,
452
+ ] ) ;
453
+ logs . length = 0 ;
454
+
455
+ // Destroy the component.
456
+ rootScope . $apply ( 'hideNg2 = true' ) ;
457
+ expect ( logs ) . toEqual ( [
458
+ 'OnDestroy(qux)' ,
459
+ ] ) ;
460
+ logs . length = 0 ;
461
+ } ) ;
462
+ } ) ;
463
+ } ) ) ;
464
+
309
465
it ( 'should detach hostViews from the ApplicationRef once destroyed' , async ( ( ) => {
310
466
let ng2Component : Ng2Component ;
311
467
@@ -339,17 +495,18 @@ export function main() {
339
495
const $injector = angular . bootstrap ( element , [ ng1Module . name ] ) ;
340
496
const $rootScope = $injector . get ( $ROOT_SCOPE ) as angular . IRootScopeService ;
341
497
342
- // Wait for the module to be bootstrapped.
343
- setTimeout ( ( ) => {
344
- const hostView : ViewRef =
345
- ( ng2Component . appRef . attachView as jasmine . Spy ) . calls . mostRecent ( ) . args [ 0 ] ;
498
+ setTimeout ( ( ) => { // Wait for the module to be bootstrapped.
499
+ setTimeout ( ( ) => { // Wait for the hostView to be attached (during the `$digest`).
500
+ const hostView : ViewRef =
501
+ ( ng2Component . appRef . attachView as jasmine . Spy ) . calls . mostRecent ( ) . args [ 0 ] ;
346
502
347
- expect ( hostView . destroyed ) . toBe ( false ) ;
503
+ expect ( hostView . destroyed ) . toBe ( false ) ;
348
504
349
- $rootScope . $apply ( 'hideNg2 = true' ) ;
505
+ $rootScope . $apply ( 'hideNg2 = true' ) ;
350
506
351
- expect ( hostView . destroyed ) . toBe ( true ) ;
352
- expect ( ng2Component . appRef . detachView ) . toHaveBeenCalledWith ( hostView ) ;
507
+ expect ( hostView . destroyed ) . toBe ( true ) ;
508
+ expect ( ng2Component . appRef . detachView ) . toHaveBeenCalledWith ( hostView ) ;
509
+ } ) ;
353
510
} ) ;
354
511
} ) ) ;
355
512
0 commit comments