4020 lines
89 KiB
TypeScript
4020 lines
89 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright Google Inc. All Rights Reserved.
|
|
*
|
|
* Use of this source code is governed by an MIT-style license that can be
|
|
* found in the LICENSE file at https://angular.io/license
|
|
*/
|
|
|
|
import {CommonModule} from '@angular/common';
|
|
import {Component, ComponentFactoryResolver, Directive, Input, NgModule, OnChanges, SimpleChanges, ViewChild, ViewContainerRef} from '@angular/core';
|
|
import {SimpleChange} from '@angular/core/src/core';
|
|
import {TestBed} from '@angular/core/testing';
|
|
import {By} from '@angular/platform-browser';
|
|
import {onlyInIvy} from '@angular/private/testing';
|
|
|
|
describe('onChanges', () => {
|
|
it('should correctly support updating one Input among many', () => {
|
|
let log: string[] = [];
|
|
|
|
@Component({selector: 'child-comp', template: 'child'})
|
|
class ChildComp implements OnChanges {
|
|
@Input() a: number = 0;
|
|
@Input() b: number = 0;
|
|
@Input() c: number = 0;
|
|
|
|
ngOnChanges(changes: SimpleChanges) {
|
|
for (let key in changes) {
|
|
const simpleChange = changes[key];
|
|
log.push(key + ': ' + simpleChange.previousValue + ' -> ' + simpleChange.currentValue);
|
|
}
|
|
}
|
|
}
|
|
|
|
@Component(
|
|
{selector: 'app-comp', template: '<child-comp [a]="a" [b]="b" [c]="c"></child-comp>'})
|
|
class AppComp {
|
|
a = 0;
|
|
b = 0;
|
|
c = 0;
|
|
}
|
|
|
|
TestBed.configureTestingModule({declarations: [AppComp, ChildComp]});
|
|
const fixture = TestBed.createComponent(AppComp);
|
|
fixture.detectChanges();
|
|
const appComp = fixture.componentInstance;
|
|
expect(log).toEqual(['a: undefined -> 0', 'b: undefined -> 0', 'c: undefined -> 0']);
|
|
log.length = 0;
|
|
|
|
appComp.a = 1;
|
|
fixture.detectChanges();
|
|
expect(log).toEqual(['a: 0 -> 1']);
|
|
log.length = 0;
|
|
|
|
appComp.b = 2;
|
|
fixture.detectChanges();
|
|
expect(log).toEqual(['b: 0 -> 2']);
|
|
log.length = 0;
|
|
|
|
appComp.c = 3;
|
|
fixture.detectChanges();
|
|
expect(log).toEqual(['c: 0 -> 3']);
|
|
});
|
|
|
|
it('should call onChanges method after inputs are set in creation and update mode', () => {
|
|
const events: any[] = [];
|
|
|
|
@Component({
|
|
selector: 'comp',
|
|
template: `<p>test</p>`,
|
|
})
|
|
class Comp {
|
|
@Input()
|
|
val1 = 'a';
|
|
|
|
@Input('publicVal2')
|
|
val2 = 'b';
|
|
|
|
ngOnChanges(changes: SimpleChanges) { events.push({name: 'comp', changes}); }
|
|
}
|
|
|
|
@Component({template: `<comp [val1]="val1" [publicVal2]="val2"></comp>`})
|
|
class App {
|
|
val1 = 'a2';
|
|
|
|
val2 = 'b2';
|
|
}
|
|
|
|
TestBed.configureTestingModule({
|
|
declarations: [App, Comp],
|
|
});
|
|
const fixture = TestBed.createComponent(App);
|
|
fixture.detectChanges();
|
|
|
|
expect(events).toEqual([{
|
|
name: 'comp',
|
|
changes: {
|
|
val1: new SimpleChange(undefined, 'a2', true),
|
|
val2: new SimpleChange(undefined, 'b2', true),
|
|
}
|
|
}]);
|
|
|
|
events.length = 0;
|
|
fixture.componentInstance.val1 = 'a3';
|
|
fixture.componentInstance.val2 = 'b3';
|
|
fixture.detectChanges();
|
|
|
|
|
|
expect(events).toEqual([{
|
|
name: 'comp',
|
|
changes: {
|
|
val1: new SimpleChange('a2', 'a3', false),
|
|
val2: new SimpleChange('b2', 'b3', false),
|
|
}
|
|
}]);
|
|
});
|
|
|
|
it('should call parent onChanges before child onChanges', () => {
|
|
const events: any[] = [];
|
|
|
|
@Component({
|
|
selector: 'parent',
|
|
template: `<child [val]="val"></child>`,
|
|
})
|
|
class Parent {
|
|
@Input()
|
|
val = '';
|
|
|
|
ngOnChanges(changes: SimpleChanges) { events.push({name: 'parent', changes}); }
|
|
}
|
|
|
|
@Component({
|
|
selector: 'child',
|
|
template: `<p>test</p>`,
|
|
})
|
|
class Child {
|
|
@Input()
|
|
val = '';
|
|
|
|
ngOnChanges(changes: SimpleChanges) { events.push({name: 'child', changes}); }
|
|
}
|
|
|
|
@Component({template: `<parent [val]="val"></parent>`})
|
|
class App {
|
|
val = 'foo';
|
|
}
|
|
|
|
TestBed.configureTestingModule({
|
|
declarations: [App, Child, Parent],
|
|
});
|
|
const fixture = TestBed.createComponent(App);
|
|
fixture.detectChanges();
|
|
|
|
expect(events).toEqual([
|
|
{
|
|
name: 'parent',
|
|
changes: {
|
|
val: new SimpleChange(undefined, 'foo', true),
|
|
}
|
|
},
|
|
{
|
|
name: 'child',
|
|
changes: {
|
|
val: new SimpleChange(undefined, 'foo', true),
|
|
}
|
|
}
|
|
]);
|
|
|
|
events.length = 0;
|
|
fixture.componentInstance.val = 'bar';
|
|
fixture.detectChanges();
|
|
|
|
expect(events).toEqual([
|
|
{
|
|
name: 'parent',
|
|
changes: {
|
|
val: new SimpleChange('foo', 'bar', false),
|
|
}
|
|
},
|
|
{
|
|
name: 'child',
|
|
changes: {
|
|
val: new SimpleChange('foo', 'bar', false),
|
|
}
|
|
}
|
|
]);
|
|
});
|
|
|
|
it('should call all parent onChanges across view before calling children onChanges', () => {
|
|
const events: any[] = [];
|
|
|
|
@Component({
|
|
selector: 'parent',
|
|
template: `<child [name]="name" [val]="val"></child>`,
|
|
})
|
|
class Parent {
|
|
@Input()
|
|
val = '';
|
|
|
|
@Input()
|
|
name = '';
|
|
|
|
ngOnChanges(changes: SimpleChanges) { events.push({name: 'parent ' + this.name, changes}); }
|
|
}
|
|
|
|
@Component({
|
|
selector: 'child',
|
|
template: `<p>test</p>`,
|
|
})
|
|
class Child {
|
|
@Input()
|
|
val = '';
|
|
|
|
@Input()
|
|
name = '';
|
|
|
|
ngOnChanges(changes: SimpleChanges) { events.push({name: 'child ' + this.name, changes}); }
|
|
}
|
|
|
|
@Component({
|
|
template: `
|
|
<parent name="1" [val]="val"></parent>
|
|
<parent name="2" [val]="val"></parent>
|
|
`
|
|
})
|
|
class App {
|
|
val = 'foo';
|
|
}
|
|
|
|
TestBed.configureTestingModule({
|
|
declarations: [App, Child, Parent],
|
|
});
|
|
const fixture = TestBed.createComponent(App);
|
|
fixture.detectChanges();
|
|
|
|
expect(events).toEqual([
|
|
{
|
|
name: 'parent 1',
|
|
changes: {
|
|
name: new SimpleChange(undefined, '1', true),
|
|
val: new SimpleChange(undefined, 'foo', true),
|
|
}
|
|
},
|
|
{
|
|
name: 'parent 2',
|
|
changes: {
|
|
name: new SimpleChange(undefined, '2', true),
|
|
val: new SimpleChange(undefined, 'foo', true),
|
|
}
|
|
},
|
|
{
|
|
name: 'child 1',
|
|
changes: {
|
|
name: new SimpleChange(undefined, '1', true),
|
|
val: new SimpleChange(undefined, 'foo', true),
|
|
}
|
|
},
|
|
{
|
|
name: 'child 2',
|
|
changes: {
|
|
name: new SimpleChange(undefined, '2', true),
|
|
val: new SimpleChange(undefined, 'foo', true),
|
|
}
|
|
},
|
|
]);
|
|
|
|
events.length = 0;
|
|
fixture.componentInstance.val = 'bar';
|
|
fixture.detectChanges();
|
|
|
|
expect(events).toEqual([
|
|
{
|
|
name: 'parent 1',
|
|
changes: {
|
|
val: new SimpleChange('foo', 'bar', false),
|
|
}
|
|
},
|
|
{
|
|
name: 'parent 2',
|
|
changes: {
|
|
val: new SimpleChange('foo', 'bar', false),
|
|
}
|
|
},
|
|
{
|
|
name: 'child 1',
|
|
changes: {
|
|
val: new SimpleChange('foo', 'bar', false),
|
|
}
|
|
},
|
|
{
|
|
name: 'child 2',
|
|
changes: {
|
|
val: new SimpleChange('foo', 'bar', false),
|
|
}
|
|
},
|
|
]);
|
|
});
|
|
|
|
it('should call onChanges every time a new view is created with ngIf', () => {
|
|
const events: any[] = [];
|
|
|
|
@Component({
|
|
selector: 'comp',
|
|
template: `<p>{{val}}</p>`,
|
|
})
|
|
class Comp {
|
|
@Input()
|
|
val = '';
|
|
|
|
ngOnChanges(changes: SimpleChanges) { events.push({name: 'comp', changes}); }
|
|
}
|
|
|
|
@Component({template: `<comp *ngIf="show" [val]="val"></comp>`})
|
|
class App {
|
|
show = true;
|
|
|
|
val = 'a';
|
|
}
|
|
|
|
TestBed.configureTestingModule({
|
|
declarations: [App, Comp],
|
|
imports: [CommonModule],
|
|
});
|
|
const fixture = TestBed.createComponent(App);
|
|
fixture.detectChanges();
|
|
|
|
expect(events).toEqual([{
|
|
name: 'comp',
|
|
changes: {
|
|
val: new SimpleChange(undefined, 'a', true),
|
|
}
|
|
}]);
|
|
|
|
events.length = 0;
|
|
fixture.componentInstance.show = false;
|
|
fixture.detectChanges();
|
|
|
|
expect(events).toEqual([]);
|
|
|
|
fixture.componentInstance.val = 'b';
|
|
fixture.componentInstance.show = true;
|
|
fixture.detectChanges();
|
|
|
|
expect(events).toEqual([{
|
|
name: 'comp',
|
|
changes: {
|
|
val: new SimpleChange(undefined, 'b', true),
|
|
}
|
|
}]);
|
|
});
|
|
|
|
it('should call onChanges in hosts before their content children', () => {
|
|
const events: any[] = [];
|
|
@Component({
|
|
selector: 'projected',
|
|
template: `<p>{{val}}</p>`,
|
|
})
|
|
class Projected {
|
|
@Input()
|
|
val = '';
|
|
|
|
ngOnChanges(changes: SimpleChanges) { events.push({name: 'projected', changes}); }
|
|
}
|
|
|
|
@Component({
|
|
selector: 'comp',
|
|
template: `<div><ng-content></ng-content></div>`,
|
|
})
|
|
class Comp {
|
|
@Input()
|
|
val = '';
|
|
|
|
ngOnChanges(changes: SimpleChanges) { events.push({name: 'comp', changes}); }
|
|
}
|
|
|
|
@Component({
|
|
template: `<comp [val]="val"><projected [val]="val"></projected></comp>`,
|
|
})
|
|
class App {
|
|
val = 'a';
|
|
}
|
|
|
|
TestBed.configureTestingModule({
|
|
declarations: [App, Comp, Projected],
|
|
});
|
|
const fixture = TestBed.createComponent(App);
|
|
fixture.detectChanges();
|
|
|
|
expect(events).toEqual([
|
|
{
|
|
name: 'comp',
|
|
changes: {
|
|
val: new SimpleChange(undefined, 'a', true),
|
|
}
|
|
},
|
|
{
|
|
name: 'projected',
|
|
changes: {
|
|
val: new SimpleChange(undefined, 'a', true),
|
|
}
|
|
},
|
|
]);
|
|
|
|
events.length = 0;
|
|
fixture.componentInstance.val = 'b';
|
|
fixture.detectChanges();
|
|
|
|
expect(events).toEqual([
|
|
{
|
|
name: 'comp',
|
|
changes: {
|
|
val: new SimpleChange('a', 'b', false),
|
|
}
|
|
},
|
|
{
|
|
name: 'projected',
|
|
changes: {
|
|
val: new SimpleChange('a', 'b', false),
|
|
}
|
|
},
|
|
]);
|
|
});
|
|
|
|
it('should call onChanges in host and its content children before next host', () => {
|
|
const events: any[] = [];
|
|
@Component({
|
|
selector: 'projected',
|
|
template: `<p>{{val}}</p>`,
|
|
})
|
|
class Projected {
|
|
@Input()
|
|
val = '';
|
|
|
|
@Input()
|
|
name = '';
|
|
|
|
ngOnChanges(changes: SimpleChanges) {
|
|
events.push({name: 'projected ' + this.name, changes});
|
|
}
|
|
}
|
|
|
|
@Component({
|
|
selector: 'comp',
|
|
template: `<div><ng-content></ng-content></div>`,
|
|
})
|
|
class Comp {
|
|
@Input()
|
|
val = '';
|
|
|
|
@Input()
|
|
name = '';
|
|
|
|
ngOnChanges(changes: SimpleChanges) { events.push({name: 'comp ' + this.name, changes}); }
|
|
}
|
|
|
|
@Component({
|
|
template: `
|
|
<comp name="1" [val]="val">
|
|
<projected name="1" [val]="val"></projected>
|
|
</comp>
|
|
<comp name="2" [val]="val">
|
|
<projected name="2" [val]="val"></projected>
|
|
</comp>
|
|
`,
|
|
})
|
|
class App {
|
|
val = 'a';
|
|
}
|
|
|
|
TestBed.configureTestingModule({
|
|
declarations: [App, Comp, Projected],
|
|
});
|
|
const fixture = TestBed.createComponent(App);
|
|
fixture.detectChanges();
|
|
|
|
expect(events).toEqual([
|
|
{
|
|
name: 'comp 1',
|
|
changes: {
|
|
name: new SimpleChange(undefined, '1', true),
|
|
val: new SimpleChange(undefined, 'a', true),
|
|
}
|
|
},
|
|
{
|
|
name: 'projected 1',
|
|
changes: {
|
|
name: new SimpleChange(undefined, '1', true),
|
|
val: new SimpleChange(undefined, 'a', true),
|
|
}
|
|
},
|
|
{
|
|
name: 'comp 2',
|
|
changes: {
|
|
name: new SimpleChange(undefined, '2', true),
|
|
val: new SimpleChange(undefined, 'a', true),
|
|
}
|
|
},
|
|
{
|
|
name: 'projected 2',
|
|
changes: {
|
|
name: new SimpleChange(undefined, '2', true),
|
|
val: new SimpleChange(undefined, 'a', true),
|
|
}
|
|
},
|
|
]);
|
|
|
|
events.length = 0;
|
|
fixture.componentInstance.val = 'b';
|
|
fixture.detectChanges();
|
|
|
|
expect(events).toEqual([
|
|
{
|
|
name: 'comp 1',
|
|
changes: {
|
|
val: new SimpleChange('a', 'b', false),
|
|
}
|
|
},
|
|
{
|
|
name: 'projected 1',
|
|
changes: {
|
|
val: new SimpleChange('a', 'b', false),
|
|
}
|
|
},
|
|
{
|
|
name: 'comp 2',
|
|
changes: {
|
|
val: new SimpleChange('a', 'b', false),
|
|
}
|
|
},
|
|
{
|
|
name: 'projected 2',
|
|
changes: {
|
|
val: new SimpleChange('a', 'b', false),
|
|
}
|
|
},
|
|
]);
|
|
});
|
|
|
|
it('should be called on directives after component by default', () => {
|
|
const events: any[] = [];
|
|
|
|
@Directive({
|
|
selector: '[dir]',
|
|
})
|
|
class Dir {
|
|
@Input()
|
|
dir = '';
|
|
|
|
ngOnChanges(changes: SimpleChanges) { events.push({name: 'dir', changes}); }
|
|
}
|
|
|
|
@Component({
|
|
selector: 'comp',
|
|
template: `<p>{{val}}</p>`,
|
|
})
|
|
class Comp {
|
|
@Input()
|
|
val = '';
|
|
|
|
ngOnChanges(changes: SimpleChanges) { events.push({name: 'comp', changes}); }
|
|
}
|
|
|
|
@Component({
|
|
template: `<comp [dir]="val" [val]="val"></comp>`,
|
|
})
|
|
class App {
|
|
val = 'a';
|
|
}
|
|
|
|
TestBed.configureTestingModule({
|
|
declarations: [App, Comp, Dir],
|
|
});
|
|
const fixture = TestBed.createComponent(App);
|
|
fixture.detectChanges();
|
|
|
|
expect(events).toEqual([
|
|
{
|
|
name: 'comp',
|
|
changes: {
|
|
val: new SimpleChange(undefined, 'a', true),
|
|
}
|
|
},
|
|
{
|
|
name: 'dir',
|
|
changes: {
|
|
dir: new SimpleChange(undefined, 'a', true),
|
|
}
|
|
}
|
|
]);
|
|
|
|
events.length = 0;
|
|
fixture.componentInstance.val = 'b';
|
|
fixture.detectChanges();
|
|
|
|
expect(events).toEqual([
|
|
{
|
|
name: 'comp',
|
|
changes: {
|
|
val: new SimpleChange('a', 'b', false),
|
|
}
|
|
},
|
|
{
|
|
name: 'dir',
|
|
changes: {
|
|
dir: new SimpleChange('a', 'b', false),
|
|
}
|
|
}
|
|
]);
|
|
});
|
|
|
|
it('should be called on directives before component if component injects directives', () => {
|
|
|
|
const events: any[] = [];
|
|
|
|
@Directive({
|
|
selector: '[dir]',
|
|
})
|
|
class Dir {
|
|
@Input()
|
|
dir = '';
|
|
|
|
ngOnChanges(changes: SimpleChanges) { events.push({name: 'dir', changes}); }
|
|
}
|
|
|
|
@Component({
|
|
selector: 'comp',
|
|
template: `<p>{{val}}</p>`,
|
|
})
|
|
class Comp {
|
|
@Input()
|
|
val = '';
|
|
|
|
constructor(public dir: Dir) {}
|
|
|
|
ngOnChanges(changes: SimpleChanges) { events.push({name: 'comp', changes}); }
|
|
}
|
|
|
|
@Component({
|
|
template: `<comp [dir]="val" [val]="val"></comp>`,
|
|
})
|
|
class App {
|
|
val = 'a';
|
|
}
|
|
|
|
TestBed.configureTestingModule({
|
|
declarations: [App, Comp, Dir],
|
|
});
|
|
const fixture = TestBed.createComponent(App);
|
|
fixture.detectChanges();
|
|
|
|
expect(events).toEqual([
|
|
{
|
|
name: 'dir',
|
|
changes: {
|
|
dir: new SimpleChange(undefined, 'a', true),
|
|
}
|
|
},
|
|
{
|
|
name: 'comp',
|
|
changes: {
|
|
val: new SimpleChange(undefined, 'a', true),
|
|
}
|
|
}
|
|
]);
|
|
|
|
events.length = 0;
|
|
fixture.componentInstance.val = 'b';
|
|
fixture.detectChanges();
|
|
|
|
expect(events).toEqual([
|
|
{
|
|
name: 'dir',
|
|
changes: {
|
|
dir: new SimpleChange('a', 'b', false),
|
|
}
|
|
},
|
|
{
|
|
name: 'comp',
|
|
changes: {
|
|
val: new SimpleChange('a', 'b', false),
|
|
}
|
|
}
|
|
]);
|
|
|
|
});
|
|
|
|
it('should be called on multiple directives in injection order', () => {
|
|
|
|
const events: any[] = [];
|
|
|
|
@Directive({
|
|
selector: '[dir]',
|
|
})
|
|
class Dir {
|
|
@Input()
|
|
dir = '';
|
|
|
|
ngOnChanges(changes: SimpleChanges) { events.push({name: 'dir', changes}); }
|
|
}
|
|
|
|
@Directive({
|
|
selector: '[injectionDir]',
|
|
})
|
|
class InjectionDir {
|
|
@Input()
|
|
injectionDir = '';
|
|
|
|
constructor(public dir: Dir) {}
|
|
|
|
ngOnChanges(changes: SimpleChanges) { events.push({name: 'injectionDir', changes}); }
|
|
}
|
|
|
|
@Component({
|
|
template: `<div [injectionDir]="val" [dir]="val"></div>`,
|
|
})
|
|
class App {
|
|
val = 'a';
|
|
}
|
|
|
|
TestBed.configureTestingModule({
|
|
declarations: [App, InjectionDir, Dir],
|
|
});
|
|
const fixture = TestBed.createComponent(App);
|
|
fixture.detectChanges();
|
|
|
|
expect(events).toEqual([
|
|
{
|
|
name: 'dir',
|
|
changes: {
|
|
dir: new SimpleChange(undefined, 'a', true),
|
|
}
|
|
},
|
|
{
|
|
name: 'injectionDir',
|
|
changes: {
|
|
injectionDir: new SimpleChange(undefined, 'a', true),
|
|
}
|
|
}
|
|
]);
|
|
});
|
|
|
|
|
|
it('should be called on directives on an element', () => {
|
|
const events: any[] = [];
|
|
|
|
@Directive({
|
|
selector: '[dir]',
|
|
})
|
|
class Dir {
|
|
@Input()
|
|
dir = '';
|
|
|
|
@Input('dir-val')
|
|
val = '';
|
|
|
|
ngOnChanges(changes: SimpleChanges) { events.push({name: 'dir', changes}); }
|
|
}
|
|
|
|
@Component({template: `<div [dir]="val1" [dir-val]="val2"></div>`})
|
|
class App {
|
|
val1 = 'a';
|
|
val2 = 'b';
|
|
}
|
|
|
|
TestBed.configureTestingModule({
|
|
declarations: [App, Dir],
|
|
});
|
|
const fixture = TestBed.createComponent(App);
|
|
fixture.detectChanges();
|
|
|
|
expect(events).toEqual([{
|
|
name: 'dir',
|
|
changes: {
|
|
dir: new SimpleChange(undefined, 'a', true),
|
|
val: new SimpleChange(undefined, 'b', true),
|
|
}
|
|
}]);
|
|
|
|
events.length = 0;
|
|
fixture.componentInstance.val1 = 'a1';
|
|
fixture.componentInstance.val2 = 'b1';
|
|
fixture.detectChanges();
|
|
expect(events).toEqual([{
|
|
name: 'dir',
|
|
changes: {
|
|
dir: new SimpleChange('a', 'a1', false),
|
|
val: new SimpleChange('b', 'b1', false),
|
|
}
|
|
}]);
|
|
});
|
|
|
|
it('should call onChanges properly in for loop', () => {
|
|
const events: any[] = [];
|
|
|
|
@Component({
|
|
selector: 'comp',
|
|
template: `<p>{{val}}</p>`,
|
|
})
|
|
class Comp {
|
|
@Input()
|
|
val = '';
|
|
|
|
@Input()
|
|
name = '';
|
|
|
|
ngOnChanges(changes: SimpleChanges) { events.push({name: 'comp ' + this.name, changes}); }
|
|
}
|
|
|
|
@Component({
|
|
template: `
|
|
<comp name="0" [val]="val"></comp>
|
|
<comp *ngFor="let number of numbers" [name]="number" [val]="val"></comp>
|
|
<comp name="1" [val]="val"></comp>
|
|
`
|
|
})
|
|
class App {
|
|
val = 'a';
|
|
|
|
numbers = ['2', '3', '4'];
|
|
}
|
|
|
|
TestBed.configureTestingModule({
|
|
declarations: [App, Comp],
|
|
imports: [CommonModule],
|
|
});
|
|
const fixture = TestBed.createComponent(App);
|
|
fixture.detectChanges();
|
|
|
|
expect(events).toEqual([
|
|
{
|
|
name: 'comp 0',
|
|
changes: {
|
|
name: new SimpleChange(undefined, '0', true),
|
|
val: new SimpleChange(undefined, 'a', true),
|
|
}
|
|
},
|
|
{
|
|
name: 'comp 1',
|
|
changes: {
|
|
name: new SimpleChange(undefined, '1', true),
|
|
val: new SimpleChange(undefined, 'a', true),
|
|
}
|
|
},
|
|
{
|
|
name: 'comp 2',
|
|
changes: {
|
|
name: new SimpleChange(undefined, '2', true),
|
|
val: new SimpleChange(undefined, 'a', true),
|
|
}
|
|
},
|
|
{
|
|
name: 'comp 3',
|
|
changes: {
|
|
name: new SimpleChange(undefined, '3', true),
|
|
val: new SimpleChange(undefined, 'a', true),
|
|
}
|
|
},
|
|
{
|
|
name: 'comp 4',
|
|
changes: {
|
|
name: new SimpleChange(undefined, '4', true),
|
|
val: new SimpleChange(undefined, 'a', true),
|
|
}
|
|
}
|
|
]);
|
|
|
|
events.length = 0;
|
|
fixture.componentInstance.val = 'b';
|
|
fixture.detectChanges();
|
|
|
|
expect(events).toEqual([
|
|
{
|
|
name: 'comp 0',
|
|
changes: {
|
|
val: new SimpleChange('a', 'b', false),
|
|
}
|
|
},
|
|
{
|
|
name: 'comp 1',
|
|
changes: {
|
|
val: new SimpleChange('a', 'b', false),
|
|
}
|
|
},
|
|
{
|
|
name: 'comp 2',
|
|
changes: {
|
|
val: new SimpleChange('a', 'b', false),
|
|
}
|
|
},
|
|
{
|
|
name: 'comp 3',
|
|
changes: {
|
|
val: new SimpleChange('a', 'b', false),
|
|
}
|
|
},
|
|
{
|
|
name: 'comp 4',
|
|
changes: {
|
|
val: new SimpleChange('a', 'b', false),
|
|
}
|
|
}
|
|
]);
|
|
});
|
|
|
|
it('should call onChanges properly in for loop with children', () => {
|
|
const events: any[] = [];
|
|
|
|
@Component({
|
|
selector: 'child',
|
|
template: `<p>{{val}}</p>`,
|
|
})
|
|
class Child {
|
|
@Input()
|
|
val = '';
|
|
|
|
@Input()
|
|
name = '';
|
|
|
|
ngOnChanges(changes: SimpleChanges) {
|
|
events.push({name: 'child of parent ' + this.name, changes});
|
|
}
|
|
}
|
|
|
|
@Component({
|
|
selector: 'parent',
|
|
template: `<child [name]="name" [val]="val"></child>`,
|
|
})
|
|
class Parent {
|
|
@Input()
|
|
val = '';
|
|
|
|
@Input()
|
|
name = '';
|
|
|
|
ngOnChanges(changes: SimpleChanges) { events.push({name: 'parent ' + this.name, changes}); }
|
|
}
|
|
|
|
@Component({
|
|
template: `
|
|
<parent name="0" [val]="val"></parent>
|
|
<parent *ngFor="let number of numbers" [name]="number" [val]="val"></parent>
|
|
<parent name="1" [val]="val"></parent>
|
|
`
|
|
})
|
|
class App {
|
|
val = 'a';
|
|
numbers = ['2', '3', '4'];
|
|
}
|
|
|
|
TestBed.configureTestingModule({
|
|
declarations: [App, Child, Parent],
|
|
imports: [CommonModule],
|
|
});
|
|
const fixture = TestBed.createComponent(App);
|
|
fixture.detectChanges();
|
|
|
|
expect(events).toEqual([
|
|
{
|
|
name: 'parent 0',
|
|
changes: {
|
|
name: new SimpleChange(undefined, '0', true),
|
|
val: new SimpleChange(undefined, 'a', true),
|
|
}
|
|
},
|
|
{
|
|
name: 'parent 1',
|
|
changes: {
|
|
name: new SimpleChange(undefined, '1', true),
|
|
val: new SimpleChange(undefined, 'a', true),
|
|
}
|
|
},
|
|
{
|
|
name: 'parent 2',
|
|
changes: {
|
|
name: new SimpleChange(undefined, '2', true),
|
|
val: new SimpleChange(undefined, 'a', true),
|
|
}
|
|
},
|
|
{
|
|
name: 'child of parent 2',
|
|
changes: {
|
|
name: new SimpleChange(undefined, '2', true),
|
|
val: new SimpleChange(undefined, 'a', true),
|
|
}
|
|
},
|
|
{
|
|
name: 'parent 3',
|
|
changes: {
|
|
name: new SimpleChange(undefined, '3', true),
|
|
val: new SimpleChange(undefined, 'a', true),
|
|
}
|
|
},
|
|
{
|
|
name: 'child of parent 3',
|
|
changes: {
|
|
name: new SimpleChange(undefined, '3', true),
|
|
val: new SimpleChange(undefined, 'a', true),
|
|
}
|
|
},
|
|
{
|
|
name: 'parent 4',
|
|
changes: {
|
|
name: new SimpleChange(undefined, '4', true),
|
|
val: new SimpleChange(undefined, 'a', true),
|
|
}
|
|
},
|
|
{
|
|
name: 'child of parent 4',
|
|
changes: {
|
|
name: new SimpleChange(undefined, '4', true),
|
|
val: new SimpleChange(undefined, 'a', true),
|
|
}
|
|
},
|
|
{
|
|
name: 'child of parent 0',
|
|
changes: {
|
|
name: new SimpleChange(undefined, '0', true),
|
|
val: new SimpleChange(undefined, 'a', true),
|
|
}
|
|
},
|
|
{
|
|
name: 'child of parent 1',
|
|
changes: {
|
|
name: new SimpleChange(undefined, '1', true),
|
|
val: new SimpleChange(undefined, 'a', true),
|
|
}
|
|
},
|
|
]);
|
|
|
|
events.length = 0;
|
|
fixture.componentInstance.val = 'b';
|
|
fixture.detectChanges();
|
|
|
|
expect(events).toEqual([
|
|
{
|
|
name: 'parent 0',
|
|
changes: {
|
|
val: new SimpleChange('a', 'b', false),
|
|
}
|
|
},
|
|
{
|
|
name: 'parent 1',
|
|
changes: {
|
|
val: new SimpleChange('a', 'b', false),
|
|
}
|
|
},
|
|
{
|
|
name: 'parent 2',
|
|
changes: {
|
|
val: new SimpleChange('a', 'b', false),
|
|
}
|
|
},
|
|
{
|
|
name: 'child of parent 2',
|
|
changes: {
|
|
val: new SimpleChange('a', 'b', false),
|
|
}
|
|
},
|
|
{
|
|
name: 'parent 3',
|
|
changes: {
|
|
val: new SimpleChange('a', 'b', false),
|
|
}
|
|
},
|
|
{
|
|
name: 'child of parent 3',
|
|
changes: {
|
|
val: new SimpleChange('a', 'b', false),
|
|
}
|
|
},
|
|
{
|
|
name: 'parent 4',
|
|
changes: {
|
|
val: new SimpleChange('a', 'b', false),
|
|
}
|
|
},
|
|
{
|
|
name: 'child of parent 4',
|
|
changes: {
|
|
val: new SimpleChange('a', 'b', false),
|
|
}
|
|
},
|
|
{
|
|
name: 'child of parent 0',
|
|
changes: {
|
|
val: new SimpleChange('a', 'b', false),
|
|
}
|
|
},
|
|
{
|
|
name: 'child of parent 1',
|
|
changes: {
|
|
val: new SimpleChange('a', 'b', false),
|
|
}
|
|
},
|
|
]);
|
|
});
|
|
|
|
it('should not call onChanges if props are set directly', () => {
|
|
const events: any[] = [];
|
|
|
|
@Component({template: `<p>{{value}}</p>`})
|
|
class App {
|
|
value = 'a';
|
|
ngOnChanges(changes: SimpleChanges) { events.push(changes); }
|
|
}
|
|
|
|
TestBed.configureTestingModule({
|
|
declarations: [App],
|
|
});
|
|
const fixture = TestBed.createComponent(App);
|
|
fixture.detectChanges();
|
|
|
|
expect(events).toEqual([]);
|
|
|
|
fixture.componentInstance.value = 'b';
|
|
fixture.detectChanges();
|
|
|
|
expect(events).toEqual([]);
|
|
});
|
|
});
|
|
|
|
it('should call all hooks in correct order when several directives on same node', () => {
|
|
let log: string[] = [];
|
|
|
|
class AllHooks {
|
|
id: number = -1;
|
|
|
|
/** @internal */
|
|
private _log(hook: string, id: number) { log.push(hook + id); }
|
|
|
|
ngOnChanges() { this._log('onChanges', this.id); }
|
|
ngOnInit() { this._log('onInit', this.id); }
|
|
ngDoCheck() { this._log('doCheck', this.id); }
|
|
ngAfterContentInit() { this._log('afterContentInit', this.id); }
|
|
ngAfterContentChecked() { this._log('afterContentChecked', this.id); }
|
|
ngAfterViewInit() { this._log('afterViewInit', this.id); }
|
|
ngAfterViewChecked() { this._log('afterViewChecked', this.id); }
|
|
}
|
|
|
|
@Directive({selector: 'div'})
|
|
class DirA extends AllHooks {
|
|
@Input('a') id: number = 0;
|
|
}
|
|
|
|
@Directive({selector: 'div'})
|
|
class DirB extends AllHooks {
|
|
@Input('b') id: number = 0;
|
|
}
|
|
|
|
@Directive({selector: 'div'})
|
|
class DirC extends AllHooks {
|
|
@Input('c') id: number = 0;
|
|
}
|
|
|
|
@Component({selector: 'app-comp', template: '<div [a]="1" [b]="2" [c]="3"></div>'})
|
|
class AppComp {
|
|
}
|
|
|
|
TestBed.configureTestingModule({declarations: [AppComp, DirA, DirB, DirC]});
|
|
const fixture = TestBed.createComponent(AppComp);
|
|
fixture.detectChanges();
|
|
|
|
expect(log).toEqual([
|
|
'onChanges1',
|
|
'onInit1',
|
|
'doCheck1',
|
|
'onChanges2',
|
|
'onInit2',
|
|
'doCheck2',
|
|
'onChanges3',
|
|
'onInit3',
|
|
'doCheck3',
|
|
'afterContentInit1',
|
|
'afterContentChecked1',
|
|
'afterContentInit2',
|
|
'afterContentChecked2',
|
|
'afterContentInit3',
|
|
'afterContentChecked3',
|
|
'afterViewInit1',
|
|
'afterViewChecked1',
|
|
'afterViewInit2',
|
|
'afterViewChecked2',
|
|
'afterViewInit3',
|
|
'afterViewChecked3'
|
|
]);
|
|
});
|
|
|
|
it('should call hooks after setting directives inputs', () => {
|
|
let log: string[] = [];
|
|
|
|
@Directive({selector: 'div'})
|
|
class DirA {
|
|
@Input() a: number = 0;
|
|
ngOnInit() { log.push('onInitA' + this.a); }
|
|
}
|
|
|
|
@Directive({selector: 'div'})
|
|
class DirB {
|
|
@Input() b: number = 0;
|
|
ngOnInit() { log.push('onInitB' + this.b); }
|
|
ngDoCheck() { log.push('doCheckB' + this.b); }
|
|
}
|
|
|
|
@Directive({selector: 'div'})
|
|
class DirC {
|
|
@Input() c: number = 0;
|
|
ngOnInit() { log.push('onInitC' + this.c); }
|
|
ngDoCheck() { log.push('doCheckC' + this.c); }
|
|
}
|
|
|
|
@Component({
|
|
selector: 'app-comp',
|
|
template: '<div [a]="id" [b]="id" [c]="id"></div><div [a]="id" [b]="id" [c]="id"></div>'
|
|
})
|
|
class AppComp {
|
|
id = 0;
|
|
}
|
|
|
|
TestBed.configureTestingModule({declarations: [AppComp, DirA, DirB, DirC]});
|
|
const fixture = TestBed.createComponent(AppComp);
|
|
fixture.detectChanges();
|
|
|
|
expect(log).toEqual([
|
|
'onInitA0', 'onInitB0', 'doCheckB0', 'onInitC0', 'doCheckC0', 'onInitA0', 'onInitB0',
|
|
'doCheckB0', 'onInitC0', 'doCheckC0'
|
|
]);
|
|
|
|
log = [];
|
|
fixture.componentInstance.id = 1;
|
|
fixture.detectChanges();
|
|
expect(log).toEqual(['doCheckB1', 'doCheckC1', 'doCheckB1', 'doCheckC1']);
|
|
});
|
|
|
|
describe('onInit', () => {
|
|
it('should call onInit after inputs are the first time', () => {
|
|
const input1Values: string[] = [];
|
|
const input2Values: string[] = [];
|
|
|
|
@Component({
|
|
selector: 'my-comp',
|
|
template: `<p>test</p>`,
|
|
})
|
|
class MyComponent {
|
|
@Input()
|
|
input1 = '';
|
|
|
|
@Input()
|
|
input2 = '';
|
|
|
|
ngOnInit() {
|
|
input1Values.push(this.input1);
|
|
input2Values.push(this.input2);
|
|
}
|
|
}
|
|
|
|
@Component({
|
|
template: `
|
|
<my-comp [input1]="value1" [input2]="value2"></my-comp>
|
|
`,
|
|
})
|
|
class App {
|
|
value1 = 'a';
|
|
value2 = 'b';
|
|
}
|
|
|
|
TestBed.configureTestingModule({
|
|
declarations: [App, MyComponent],
|
|
});
|
|
const fixture = TestBed.createComponent(App);
|
|
fixture.detectChanges();
|
|
|
|
expect(input1Values).toEqual(['a']);
|
|
expect(input2Values).toEqual(['b']);
|
|
|
|
fixture.componentInstance.value1 = 'c';
|
|
fixture.componentInstance.value2 = 'd';
|
|
fixture.detectChanges();
|
|
|
|
// Shouldn't be called again just because change detection ran.
|
|
expect(input1Values).toEqual(['a']);
|
|
expect(input2Values).toEqual(['b']);
|
|
});
|
|
|
|
it('should be called on root component', () => {
|
|
let onInitCalled = 0;
|
|
|
|
@Component({template: ``})
|
|
class App {
|
|
ngOnInit() { onInitCalled++; }
|
|
}
|
|
|
|
TestBed.configureTestingModule({
|
|
declarations: [App],
|
|
});
|
|
const fixture = TestBed.createComponent(App);
|
|
fixture.detectChanges();
|
|
|
|
expect(onInitCalled).toBe(1);
|
|
});
|
|
|
|
it('should call parent onInit before it calls child onInit', () => {
|
|
const initCalls: string[] = [];
|
|
|
|
@Component({
|
|
selector: `child-comp`,
|
|
template: `<p>child</p>`,
|
|
})
|
|
class ChildComp {
|
|
ngOnInit() { initCalls.push('child'); }
|
|
}
|
|
|
|
@Component({
|
|
template: `<child-comp></child-comp>`,
|
|
})
|
|
class ParentComp {
|
|
ngOnInit() { initCalls.push('parent'); }
|
|
}
|
|
|
|
TestBed.configureTestingModule({
|
|
declarations: [ParentComp, ChildComp],
|
|
});
|
|
const fixture = TestBed.createComponent(ParentComp);
|
|
fixture.detectChanges();
|
|
|
|
expect(initCalls).toEqual(['parent', 'child']);
|
|
});
|
|
|
|
it('should call all parent onInits across view before calling children onInits', () => {
|
|
const initCalls: string[] = [];
|
|
|
|
@Component({
|
|
selector: `child-comp`,
|
|
template: `<p>child</p>`,
|
|
})
|
|
class ChildComp {
|
|
@Input()
|
|
name = '';
|
|
|
|
ngOnInit() { initCalls.push(`child of parent ${this.name}`); }
|
|
}
|
|
|
|
@Component({
|
|
selector: 'parent-comp',
|
|
template: `<child-comp [name]="name"></child-comp>`,
|
|
})
|
|
class ParentComp {
|
|
@Input()
|
|
name = '';
|
|
|
|
ngOnInit() { initCalls.push(`parent ${this.name}`); }
|
|
}
|
|
|
|
@Component({
|
|
template: `
|
|
<parent-comp name="1"></parent-comp>
|
|
<parent-comp name="2"></parent-comp>
|
|
`
|
|
})
|
|
class App {
|
|
}
|
|
|
|
TestBed.configureTestingModule({
|
|
declarations: [App, ParentComp, ChildComp],
|
|
});
|
|
const fixture = TestBed.createComponent(App);
|
|
fixture.detectChanges();
|
|
|
|
expect(initCalls).toEqual(['parent 1', 'parent 2', 'child of parent 1', 'child of parent 2']);
|
|
});
|
|
|
|
it('should call onInit every time a new view is created (if block)', () => {
|
|
let onInitCalls = 0;
|
|
|
|
@Component({selector: 'my-comp', template: '<p>test</p>'})
|
|
class MyComp {
|
|
ngOnInit() { onInitCalls++; }
|
|
}
|
|
|
|
@Component({
|
|
template: `
|
|
<div *ngIf="show"><my-comp></my-comp></div>
|
|
`
|
|
})
|
|
class App {
|
|
show = true;
|
|
}
|
|
TestBed.configureTestingModule({
|
|
declarations: [App, MyComp],
|
|
});
|
|
const fixture = TestBed.createComponent(App);
|
|
fixture.detectChanges();
|
|
|
|
expect(onInitCalls).toBe(1);
|
|
|
|
fixture.componentInstance.show = false;
|
|
fixture.detectChanges();
|
|
|
|
expect(onInitCalls).toBe(1);
|
|
|
|
fixture.componentInstance.show = true;
|
|
fixture.detectChanges();
|
|
|
|
expect(onInitCalls).toBe(2);
|
|
});
|
|
|
|
it('should call onInit for children of dynamically created components', () => {
|
|
@Component({selector: 'my-comp', template: '<p>test</p>'})
|
|
class MyComp {
|
|
onInitCalled = false;
|
|
|
|
ngOnInit() { this.onInitCalled = true; }
|
|
}
|
|
|
|
@Component({
|
|
selector: 'dynamic-comp',
|
|
template: `
|
|
<my-comp></my-comp>
|
|
`,
|
|
})
|
|
class DynamicComp {
|
|
}
|
|
|
|
@Component({
|
|
template: `
|
|
<div #container></div>
|
|
`,
|
|
})
|
|
class App {
|
|
@ViewChild('container', {read: ViewContainerRef})
|
|
viewContainerRef !: ViewContainerRef;
|
|
|
|
constructor(public compFactoryResolver: ComponentFactoryResolver) {}
|
|
|
|
createDynamicView() {
|
|
const dynamicCompFactory = this.compFactoryResolver.resolveComponentFactory(DynamicComp);
|
|
this.viewContainerRef.createComponent(dynamicCompFactory);
|
|
}
|
|
}
|
|
|
|
// View Engine requires that DynamicComp be in entryComponents.
|
|
@NgModule({
|
|
declarations: [App, MyComp, DynamicComp],
|
|
entryComponents: [DynamicComp, App],
|
|
})
|
|
class AppModule {
|
|
}
|
|
|
|
TestBed.configureTestingModule({imports: [AppModule]});
|
|
|
|
const fixture = TestBed.createComponent(App);
|
|
fixture.detectChanges();
|
|
|
|
fixture.componentInstance.createDynamicView();
|
|
fixture.detectChanges();
|
|
|
|
const myComp = fixture.debugElement.query(By.directive(MyComp)).componentInstance;
|
|
expect(myComp.onInitCalled).toBe(true);
|
|
});
|
|
|
|
it('should call onInit in hosts before their content children', () => {
|
|
const initialized: string[] = [];
|
|
|
|
@Component({
|
|
selector: 'projected',
|
|
template: '',
|
|
})
|
|
class Projected {
|
|
ngOnInit() { initialized.push('projected'); }
|
|
}
|
|
|
|
@Component({
|
|
selector: 'comp',
|
|
template: `<ng-content></ng-content>`,
|
|
})
|
|
class Comp {
|
|
ngOnInit() { initialized.push('comp'); }
|
|
}
|
|
|
|
@Component({
|
|
template: `
|
|
<comp>
|
|
<projected></projected>
|
|
</comp>
|
|
`
|
|
})
|
|
class App {
|
|
ngOnInit() { initialized.push('app'); }
|
|
}
|
|
|
|
TestBed.configureTestingModule({
|
|
declarations: [App, Comp, Projected],
|
|
});
|
|
const fixture = TestBed.createComponent(App);
|
|
fixture.detectChanges();
|
|
|
|
expect(initialized).toEqual(['app', 'comp', 'projected']);
|
|
});
|
|
|
|
|
|
it('should call onInit in host and its content children before next host', () => {
|
|
const initialized: string[] = [];
|
|
|
|
@Component({
|
|
selector: 'projected',
|
|
template: '',
|
|
})
|
|
class Projected {
|
|
@Input()
|
|
name = '';
|
|
|
|
ngOnInit() { initialized.push('projected ' + this.name); }
|
|
}
|
|
|
|
@Component({
|
|
selector: 'comp',
|
|
template: `<ng-content></ng-content>`,
|
|
})
|
|
class Comp {
|
|
@Input()
|
|
name = '';
|
|
|
|
ngOnInit() { initialized.push('comp ' + this.name); }
|
|
}
|
|
|
|
@Component({
|
|
template: `
|
|
<comp name="1">
|
|
<projected name="1"></projected>
|
|
</comp>
|
|
<comp name="2">
|
|
<projected name="2"></projected>
|
|
</comp>
|
|
`
|
|
})
|
|
class App {
|
|
ngOnInit() { initialized.push('app'); }
|
|
}
|
|
|
|
TestBed.configureTestingModule({
|
|
declarations: [App, Comp, Projected],
|
|
});
|
|
const fixture = TestBed.createComponent(App);
|
|
fixture.detectChanges();
|
|
|
|
expect(initialized).toEqual(['app', 'comp 1', 'projected 1', 'comp 2', 'projected 2']);
|
|
});
|
|
|
|
it('should be called on directives after component by default', () => {
|
|
const initialized: string[] = [];
|
|
|
|
@Directive({
|
|
selector: '[dir]',
|
|
})
|
|
class Dir {
|
|
@Input('dir-name')
|
|
name = '';
|
|
|
|
ngOnInit() { initialized.push('dir ' + this.name); }
|
|
}
|
|
|
|
@Component({
|
|
selector: 'comp',
|
|
template: `<p></p>`,
|
|
})
|
|
class Comp {
|
|
@Input()
|
|
name = '';
|
|
|
|
ngOnInit() { initialized.push('comp ' + this.name); }
|
|
}
|
|
|
|
@Component({
|
|
template: `
|
|
<comp name="1" dir dir-name="1"></comp>
|
|
<comp name="2" dir dir-name="2"></comp>
|
|
`
|
|
})
|
|
class App {
|
|
ngOnInit() { initialized.push('app'); }
|
|
}
|
|
|
|
TestBed.configureTestingModule({
|
|
declarations: [App, Comp, Dir],
|
|
});
|
|
const fixture = TestBed.createComponent(App);
|
|
fixture.detectChanges();
|
|
|
|
expect(initialized).toEqual(['app', 'comp 1', 'dir 1', 'comp 2', 'dir 2']);
|
|
});
|
|
|
|
it('should be called on multiple directives in injection order', () => {
|
|
|
|
const events: any[] = [];
|
|
|
|
@Directive({
|
|
selector: '[dir]',
|
|
})
|
|
class Dir {
|
|
@Input()
|
|
dir = '';
|
|
|
|
ngOnInit() { events.push('dir'); }
|
|
}
|
|
|
|
@Directive({
|
|
selector: '[injectionDir]',
|
|
})
|
|
class InjectionDir {
|
|
@Input()
|
|
injectionDir = '';
|
|
|
|
constructor(public dir: Dir) {}
|
|
|
|
ngOnInit() { events.push('injectionDir'); }
|
|
}
|
|
|
|
@Component({
|
|
template: `<div [injectionDir]="val" [dir]="val"></div>`,
|
|
})
|
|
class App {
|
|
val = 'a';
|
|
|
|
ngOnInit() { events.push('app'); }
|
|
}
|
|
|
|
TestBed.configureTestingModule({
|
|
declarations: [App, InjectionDir, Dir],
|
|
});
|
|
const fixture = TestBed.createComponent(App);
|
|
fixture.detectChanges();
|
|
|
|
expect(events).toEqual(['app', 'dir', 'injectionDir']);
|
|
});
|
|
|
|
it('should be called on directives before component if component injects directives', () => {
|
|
const initialized: string[] = [];
|
|
|
|
@Directive({
|
|
selector: '[dir]',
|
|
})
|
|
class Dir {
|
|
@Input('dir-name')
|
|
name = '';
|
|
|
|
ngOnInit() { initialized.push('dir ' + this.name); }
|
|
}
|
|
|
|
@Component({
|
|
selector: 'comp',
|
|
template: `<p></p>`,
|
|
})
|
|
class Comp {
|
|
@Input()
|
|
name = '';
|
|
|
|
constructor(public dir: Dir) {}
|
|
|
|
ngOnInit() { initialized.push('comp ' + this.name); }
|
|
}
|
|
|
|
@Component({
|
|
template: `
|
|
<comp name="1" dir dir-name="1"></comp>
|
|
<comp name="2" dir dir-name="2"></comp>
|
|
`
|
|
})
|
|
class App {
|
|
ngOnInit() { initialized.push('app'); }
|
|
}
|
|
|
|
TestBed.configureTestingModule({
|
|
declarations: [App, Comp, Dir],
|
|
});
|
|
const fixture = TestBed.createComponent(App);
|
|
fixture.detectChanges();
|
|
|
|
expect(initialized).toEqual(['app', 'dir 1', 'comp 1', 'dir 2', 'comp 2']);
|
|
});
|
|
|
|
it('should be called on directives on an element', () => {
|
|
const initialized: string[] = [];
|
|
|
|
@Directive({
|
|
selector: '[dir]',
|
|
})
|
|
class Dir {
|
|
@Input('dir-name')
|
|
name = '';
|
|
|
|
ngOnInit() { initialized.push('dir ' + this.name); }
|
|
}
|
|
|
|
@Component({
|
|
template: `
|
|
<p name="1" dir dir-name="1"></p>
|
|
<p name="2" dir dir-name="2"></p>
|
|
`
|
|
})
|
|
class App {
|
|
ngOnInit() { initialized.push('app'); }
|
|
}
|
|
|
|
TestBed.configureTestingModule({
|
|
declarations: [App, Dir],
|
|
});
|
|
const fixture = TestBed.createComponent(App);
|
|
fixture.detectChanges();
|
|
|
|
expect(initialized).toEqual(['app', 'dir 1', 'dir 2']);
|
|
});
|
|
|
|
|
|
it('should call onInit properly in for loop', () => {
|
|
const initialized: string[] = [];
|
|
|
|
@Component({
|
|
selector: 'comp',
|
|
template: `<p></p>`,
|
|
})
|
|
class Comp {
|
|
@Input()
|
|
name = '';
|
|
|
|
ngOnInit() { initialized.push('comp ' + this.name); }
|
|
}
|
|
|
|
@Component({
|
|
template: `
|
|
<comp name="0"></comp>
|
|
<comp *ngFor="let number of numbers" [name]="number"></comp>
|
|
<comp name="1"></comp>
|
|
`
|
|
})
|
|
class App {
|
|
numbers = [2, 3, 4, 5, 6];
|
|
}
|
|
|
|
TestBed.configureTestingModule({
|
|
declarations: [App, Comp],
|
|
imports: [CommonModule],
|
|
});
|
|
const fixture = TestBed.createComponent(App);
|
|
fixture.detectChanges();
|
|
|
|
expect(initialized).toEqual([
|
|
'comp 0', 'comp 1', 'comp 2', 'comp 3', 'comp 4', 'comp 5', 'comp 6'
|
|
]);
|
|
});
|
|
|
|
it('should call onInit properly in for loop with children', () => {
|
|
const initialized: string[] = [];
|
|
|
|
@Component({
|
|
selector: 'child',
|
|
template: `<p></p>`,
|
|
})
|
|
class Child {
|
|
@Input()
|
|
name = '';
|
|
|
|
ngOnInit() { initialized.push('child of parent ' + this.name); }
|
|
}
|
|
|
|
@Component({selector: 'parent', template: '<child [name]="name"></child>'})
|
|
class Parent {
|
|
@Input()
|
|
name = '';
|
|
|
|
ngOnInit() { initialized.push('parent ' + this.name); }
|
|
}
|
|
|
|
@Component({
|
|
template: `
|
|
<parent name="0"></parent>
|
|
<parent *ngFor="let number of numbers" [name]="number"></parent>
|
|
<parent name="1"></parent>
|
|
`
|
|
})
|
|
class App {
|
|
numbers = [2, 3, 4, 5, 6];
|
|
}
|
|
|
|
TestBed.configureTestingModule({
|
|
declarations: [App, Child, Parent],
|
|
imports: [CommonModule],
|
|
});
|
|
const fixture = TestBed.createComponent(App);
|
|
fixture.detectChanges();
|
|
|
|
expect(initialized).toEqual([
|
|
// First the two root level components
|
|
'parent 0',
|
|
'parent 1',
|
|
|
|
// Then our 5 embedded views
|
|
'parent 2',
|
|
'child of parent 2',
|
|
'parent 3',
|
|
'child of parent 3',
|
|
'parent 4',
|
|
'child of parent 4',
|
|
'parent 5',
|
|
'child of parent 5',
|
|
'parent 6',
|
|
'child of parent 6',
|
|
|
|
// Then the children of the root level components
|
|
'child of parent 0',
|
|
'child of parent 1',
|
|
]);
|
|
});
|
|
});
|
|
|
|
describe('doCheck', () => {
|
|
it('should call doCheck on every refresh', () => {
|
|
let doCheckCalled = 0;
|
|
|
|
@Component({template: ``})
|
|
class App {
|
|
ngDoCheck() { doCheckCalled++; }
|
|
}
|
|
|
|
TestBed.configureTestingModule({
|
|
declarations: [App],
|
|
});
|
|
const fixture = TestBed.createComponent(App);
|
|
fixture.detectChanges();
|
|
|
|
expect(doCheckCalled).toBe(1);
|
|
|
|
fixture.detectChanges();
|
|
|
|
expect(doCheckCalled).toBe(2);
|
|
});
|
|
|
|
it('should call parent doCheck before child doCheck', () => {
|
|
const doChecks: string[] = [];
|
|
|
|
@Component({
|
|
selector: 'parent',
|
|
template: `<child></child>`,
|
|
})
|
|
class Parent {
|
|
ngDoCheck() { doChecks.push('parent'); }
|
|
}
|
|
|
|
@Component({
|
|
selector: 'child',
|
|
template: ``,
|
|
})
|
|
class Child {
|
|
ngDoCheck() { doChecks.push('child'); }
|
|
}
|
|
|
|
@Component({template: `<parent></parent>`})
|
|
class App {
|
|
ngDoCheck() { doChecks.push('app'); }
|
|
}
|
|
|
|
TestBed.configureTestingModule({
|
|
declarations: [App, Parent, Child],
|
|
});
|
|
const fixture = TestBed.createComponent(App);
|
|
fixture.detectChanges();
|
|
|
|
expect(doChecks).toEqual(['app', 'parent', 'child']);
|
|
});
|
|
|
|
it('should call ngOnInit before ngDoCheck if creation mode', () => {
|
|
const events: string[] = [];
|
|
@Component({template: ``})
|
|
class App {
|
|
ngOnInit() { events.push('onInit'); }
|
|
|
|
ngDoCheck() { events.push('doCheck'); }
|
|
}
|
|
|
|
TestBed.configureTestingModule({
|
|
declarations: [App],
|
|
});
|
|
const fixture = TestBed.createComponent(App);
|
|
fixture.detectChanges();
|
|
|
|
expect(events).toEqual(['onInit', 'doCheck']);
|
|
});
|
|
|
|
it('should be called on directives after component by default', () => {
|
|
const doChecks: string[] = [];
|
|
@Directive({
|
|
selector: '[dir]',
|
|
})
|
|
class Dir {
|
|
@Input('dir')
|
|
name = '';
|
|
|
|
ngDoCheck() { doChecks.push('dir ' + this.name); }
|
|
}
|
|
|
|
@Component({
|
|
selector: 'comp',
|
|
template: `<p>test</p>`,
|
|
})
|
|
class Comp {
|
|
@Input()
|
|
name = '';
|
|
|
|
ngDoCheck() { doChecks.push('comp ' + this.name); }
|
|
}
|
|
|
|
@Component({
|
|
template: `
|
|
<comp name="1" dir="1"></comp>
|
|
<comp name="2" dir="2"></comp>
|
|
`
|
|
})
|
|
class App {
|
|
ngDoCheck() { doChecks.push('app'); }
|
|
}
|
|
|
|
TestBed.configureTestingModule({
|
|
declarations: [App, Comp, Dir],
|
|
});
|
|
const fixture = TestBed.createComponent(App);
|
|
fixture.detectChanges();
|
|
|
|
expect(doChecks).toEqual(['app', 'comp 1', 'dir 1', 'comp 2', 'dir 2']);
|
|
});
|
|
|
|
it('should be called on directives before component if component injects directives', () => {
|
|
const doChecks: string[] = [];
|
|
@Directive({
|
|
selector: '[dir]',
|
|
})
|
|
class Dir {
|
|
@Input('dir')
|
|
name = '';
|
|
|
|
ngDoCheck() { doChecks.push('dir ' + this.name); }
|
|
}
|
|
|
|
@Component({
|
|
selector: 'comp',
|
|
template: `<p>test</p>`,
|
|
})
|
|
class Comp {
|
|
@Input()
|
|
name = '';
|
|
|
|
constructor(public dir: Dir) {}
|
|
|
|
ngDoCheck() { doChecks.push('comp ' + this.name); }
|
|
}
|
|
|
|
@Component({
|
|
template: `
|
|
<comp name="1" dir="1"></comp>
|
|
<comp name="2" dir="2"></comp>
|
|
`
|
|
})
|
|
class App {
|
|
ngDoCheck() { doChecks.push('app'); }
|
|
}
|
|
|
|
TestBed.configureTestingModule({
|
|
declarations: [App, Comp, Dir],
|
|
});
|
|
const fixture = TestBed.createComponent(App);
|
|
fixture.detectChanges();
|
|
|
|
expect(doChecks).toEqual(['app', 'dir 1', 'comp 1', 'dir 2', 'comp 2']);
|
|
});
|
|
|
|
it('should be called on multiple directives in injection order', () => {
|
|
|
|
const events: any[] = [];
|
|
|
|
@Directive({
|
|
selector: '[dir]',
|
|
})
|
|
class Dir {
|
|
@Input()
|
|
dir = '';
|
|
|
|
ngDoCheck() { events.push('dir'); }
|
|
}
|
|
|
|
@Directive({
|
|
selector: '[injectionDir]',
|
|
})
|
|
class InjectionDir {
|
|
@Input()
|
|
injectionDir = '';
|
|
|
|
constructor(public dir: Dir) {}
|
|
|
|
ngDoCheck() { events.push('injectionDir'); }
|
|
}
|
|
|
|
@Component({
|
|
template: `<div [injectionDir]="val" [dir]="val"></div>`,
|
|
})
|
|
class App {
|
|
val = 'a';
|
|
|
|
ngDoCheck() { events.push('app'); }
|
|
}
|
|
|
|
TestBed.configureTestingModule({
|
|
declarations: [App, InjectionDir, Dir],
|
|
});
|
|
const fixture = TestBed.createComponent(App);
|
|
fixture.detectChanges();
|
|
|
|
expect(events).toEqual(['app', 'dir', 'injectionDir']);
|
|
});
|
|
|
|
it('should be called on directives on an element', () => {
|
|
const doChecks: string[] = [];
|
|
|
|
@Directive({
|
|
selector: '[dir]',
|
|
})
|
|
class Dir {
|
|
@Input('dir')
|
|
name = '';
|
|
|
|
ngDoCheck() { doChecks.push('dir ' + this.name); }
|
|
}
|
|
|
|
@Component({
|
|
template: `
|
|
<p dir="1"></p>
|
|
<p dir="2"></p>
|
|
`
|
|
})
|
|
class App {
|
|
ngDoCheck() { doChecks.push('app'); }
|
|
}
|
|
|
|
TestBed.configureTestingModule({
|
|
declarations: [App, Dir],
|
|
});
|
|
const fixture = TestBed.createComponent(App);
|
|
fixture.detectChanges();
|
|
|
|
expect(doChecks).toEqual(['app', 'dir 1', 'dir 2']);
|
|
});
|
|
});
|
|
|
|
describe('afterContentinit', () => {
|
|
it('should be called only in creation mode', () => {
|
|
let afterContentInitCalls = 0;
|
|
|
|
@Component({
|
|
selector: 'comp',
|
|
template: `<p>test</p>`,
|
|
})
|
|
class Comp {
|
|
ngAfterContentInit() { afterContentInitCalls++; }
|
|
}
|
|
@Component({template: `<comp></comp>`})
|
|
class App {
|
|
}
|
|
|
|
TestBed.configureTestingModule({
|
|
declarations: [App, Comp],
|
|
});
|
|
const fixture = TestBed.createComponent(App);
|
|
fixture.detectChanges();
|
|
|
|
// two updates
|
|
fixture.detectChanges();
|
|
fixture.detectChanges();
|
|
|
|
expect(afterContentInitCalls).toBe(1);
|
|
});
|
|
|
|
it('should be called on root component in creation mode', () => {
|
|
let afterContentInitCalls = 0;
|
|
|
|
@Component({template: `<p>test</p>`})
|
|
class App {
|
|
ngAfterContentInit() { afterContentInitCalls++; }
|
|
}
|
|
|
|
TestBed.configureTestingModule({
|
|
declarations: [App],
|
|
});
|
|
const fixture = TestBed.createComponent(App);
|
|
fixture.detectChanges();
|
|
|
|
// two updates
|
|
fixture.detectChanges();
|
|
fixture.detectChanges();
|
|
|
|
expect(afterContentInitCalls).toBe(1);
|
|
});
|
|
|
|
it('should be called on every create ngIf', () => {
|
|
const events: string[] = [];
|
|
|
|
@Component({
|
|
selector: 'comp',
|
|
template: `<p>test</p>`,
|
|
})
|
|
class Comp {
|
|
ngAfterContentInit() { events.push('comp afterContentInit'); }
|
|
}
|
|
|
|
@Component({template: `<comp *ngIf="show"></comp>`})
|
|
class App {
|
|
show = true;
|
|
|
|
ngAfterContentInit() { events.push('app afterContentInit'); }
|
|
}
|
|
|
|
TestBed.configureTestingModule({
|
|
declarations: [App, Comp],
|
|
imports: [CommonModule],
|
|
});
|
|
const fixture = TestBed.createComponent(App);
|
|
fixture.detectChanges();
|
|
|
|
expect(events).toEqual(['app afterContentInit', 'comp afterContentInit']);
|
|
|
|
fixture.componentInstance.show = false;
|
|
fixture.detectChanges();
|
|
|
|
expect(events).toEqual(['app afterContentInit', 'comp afterContentInit']);
|
|
|
|
fixture.componentInstance.show = true;
|
|
fixture.detectChanges();
|
|
|
|
|
|
expect(events).toEqual(
|
|
['app afterContentInit', 'comp afterContentInit', 'comp afterContentInit']);
|
|
});
|
|
|
|
it('should be called in parents before children', () => {
|
|
const events: string[] = [];
|
|
|
|
@Component({
|
|
selector: 'parent',
|
|
template: `<child [name]="name"></child>`,
|
|
})
|
|
class Parent {
|
|
@Input()
|
|
name = '';
|
|
|
|
ngAfterContentInit() { events.push('parent ' + this.name); }
|
|
}
|
|
|
|
@Component({
|
|
selector: 'child',
|
|
template: `<p>test</p>`,
|
|
})
|
|
class Child {
|
|
@Input()
|
|
name = '';
|
|
|
|
ngAfterContentInit() { events.push('child of parent ' + this.name); }
|
|
}
|
|
|
|
@Component({
|
|
template: `
|
|
<parent name="1"></parent>
|
|
<parent name="2"></parent>
|
|
`
|
|
})
|
|
class App {
|
|
ngAfterContentInit() { events.push('app'); }
|
|
}
|
|
|
|
TestBed.configureTestingModule({
|
|
declarations: [App, Parent, Child],
|
|
});
|
|
const fixture = TestBed.createComponent(App);
|
|
fixture.detectChanges();
|
|
|
|
expect(events).toEqual(
|
|
['app', 'parent 1', 'parent 2', 'child of parent 1', 'child of parent 2']);
|
|
});
|
|
|
|
it('should be called in projected components before their hosts', () => {
|
|
const events: string[] = [];
|
|
|
|
@Component({
|
|
selector: 'projected-child',
|
|
template: `<p>test</p>`,
|
|
})
|
|
class ProjectedChild {
|
|
@Input()
|
|
name = '';
|
|
|
|
ngAfterContentInit() { events.push('projected child ' + this.name); }
|
|
}
|
|
|
|
@Component({
|
|
selector: 'comp',
|
|
template: `<div><ng-content></ng-content></div>`,
|
|
})
|
|
class Comp {
|
|
@Input()
|
|
name = '';
|
|
|
|
ngAfterContentInit() { events.push('comp ' + this.name); }
|
|
}
|
|
|
|
@Component({
|
|
selector: 'projected',
|
|
template: `<projected-child [name]=name></projected-child>`,
|
|
})
|
|
class Projected {
|
|
@Input()
|
|
name = '';
|
|
|
|
ngAfterContentInit() { events.push('projected ' + this.name); }
|
|
}
|
|
|
|
@Component({
|
|
template: `
|
|
<comp name="1">
|
|
<projected name="1"></projected>
|
|
<projected name="2"></projected>
|
|
</comp>
|
|
<comp name="2">
|
|
<projected name="3"></projected>
|
|
<projected name="4"></projected>
|
|
</comp>
|
|
`
|
|
})
|
|
class App {
|
|
ngAfterContentInit() { events.push('app'); }
|
|
}
|
|
|
|
TestBed.configureTestingModule({
|
|
declarations: [App, Comp, Projected, ProjectedChild],
|
|
});
|
|
const fixture = TestBed.createComponent(App);
|
|
fixture.detectChanges();
|
|
|
|
expect(events).toEqual([
|
|
// root
|
|
'app',
|
|
// projections of comp 1
|
|
'projected 1',
|
|
'projected 2',
|
|
// comp 1
|
|
'comp 1',
|
|
// projections of comp 2
|
|
'projected 3',
|
|
'projected 4',
|
|
// comp 2
|
|
'comp 2',
|
|
// children of projections
|
|
'projected child 1',
|
|
'projected child 2',
|
|
'projected child 3',
|
|
'projected child 4',
|
|
]);
|
|
});
|
|
|
|
it('should be called in correct order in a for loop', () => {
|
|
const events: string[] = [];
|
|
|
|
@Component({
|
|
selector: 'comp',
|
|
template: `<p>test</p>`,
|
|
})
|
|
class Comp {
|
|
@Input()
|
|
name = '';
|
|
|
|
ngAfterContentInit() { events.push('comp ' + this.name); }
|
|
}
|
|
|
|
@Component({
|
|
template: `
|
|
<comp name="4"></comp>
|
|
<comp *ngFor="let number of numbers" [name]="number"></comp>
|
|
<comp name="5"></comp>
|
|
`
|
|
})
|
|
class App {
|
|
numbers = [0, 1, 2, 3];
|
|
|
|
ngAfterContentInit() { events.push('app'); }
|
|
}
|
|
|
|
TestBed.configureTestingModule({
|
|
declarations: [App, Comp],
|
|
imports: [CommonModule],
|
|
});
|
|
const fixture = TestBed.createComponent(App);
|
|
fixture.detectChanges();
|
|
|
|
expect(events).toEqual(['app', 'comp 0', 'comp 1', 'comp 2', 'comp 3', 'comp 4', 'comp 5']);
|
|
});
|
|
|
|
it('should be called in correct order in a for loop with children', () => {
|
|
const events: string[] = [];
|
|
|
|
@Component({
|
|
selector: 'parent',
|
|
template: `<child [name]=name></child>`,
|
|
})
|
|
class Parent {
|
|
@Input()
|
|
name = '';
|
|
|
|
ngAfterContentInit() { events.push('parent ' + this.name); }
|
|
}
|
|
|
|
@Component({
|
|
selector: 'child',
|
|
template: `<p>test</p>`,
|
|
})
|
|
class Child {
|
|
@Input()
|
|
name = '';
|
|
|
|
ngAfterContentInit() { events.push('child of parent ' + this.name); }
|
|
}
|
|
|
|
@Component({
|
|
template: `
|
|
<parent name="4"></parent>
|
|
<parent *ngFor="let number of numbers" [name]="number"></parent>
|
|
<parent name="5"></parent>
|
|
`
|
|
})
|
|
class App {
|
|
numbers = [0, 1, 2, 3];
|
|
ngAfterContentInit() { events.push('app'); }
|
|
}
|
|
|
|
TestBed.configureTestingModule({
|
|
declarations: [App, Parent, Child],
|
|
});
|
|
const fixture = TestBed.createComponent(App);
|
|
fixture.detectChanges();
|
|
|
|
expect(events).toEqual([
|
|
// root
|
|
'app',
|
|
// 4 embedded views
|
|
'parent 0',
|
|
'child of parent 0',
|
|
'parent 1',
|
|
'child of parent 1',
|
|
'parent 2',
|
|
'child of parent 2',
|
|
'parent 3',
|
|
'child of parent 3',
|
|
// root children
|
|
'parent 4',
|
|
'parent 5',
|
|
// children of root children
|
|
'child of parent 4',
|
|
'child of parent 5',
|
|
]);
|
|
});
|
|
|
|
it('should be called on directives after component', () => {
|
|
const events: string[] = [];
|
|
@Directive({
|
|
selector: '[dir]',
|
|
})
|
|
class Dir {
|
|
@Input('dir')
|
|
name = '';
|
|
|
|
ngAfterContentInit() { events.push('dir ' + this.name); }
|
|
}
|
|
|
|
@Component({
|
|
selector: 'comp',
|
|
template: `<p>test</p>`,
|
|
})
|
|
class Comp {
|
|
@Input()
|
|
name = '';
|
|
|
|
ngAfterContentInit() { events.push('comp ' + this.name); }
|
|
}
|
|
|
|
@Component({
|
|
template: `
|
|
<comp name="1" dir="1"></comp>
|
|
<comp name="2" dir="2"></comp>
|
|
`
|
|
})
|
|
class App {
|
|
ngAfterContentInit() { events.push('app'); }
|
|
}
|
|
|
|
TestBed.configureTestingModule({
|
|
declarations: [App, Comp, Dir],
|
|
});
|
|
const fixture = TestBed.createComponent(App);
|
|
fixture.detectChanges();
|
|
|
|
expect(events).toEqual([
|
|
'app',
|
|
'comp 1',
|
|
'dir 1',
|
|
'comp 2',
|
|
'dir 2',
|
|
]);
|
|
});
|
|
});
|
|
|
|
describe('afterContentChecked', () => {
|
|
it('should be called every change detection run after afterContentInit', () => {
|
|
const events: string[] = [];
|
|
|
|
@Component({
|
|
selector: 'comp',
|
|
template: `<p>test</p>`,
|
|
})
|
|
class Comp {
|
|
ngAfterContentInit() { events.push('comp afterContentInit'); }
|
|
|
|
ngAfterContentChecked() { events.push('comp afterContentChecked'); }
|
|
}
|
|
|
|
@Component({template: `<comp></comp>`})
|
|
class App {
|
|
ngAfterContentInit() { events.push('app afterContentInit'); }
|
|
|
|
ngAfterContentChecked() { events.push('app afterContentChecked'); }
|
|
}
|
|
|
|
TestBed.configureTestingModule({
|
|
declarations: [App, Comp],
|
|
});
|
|
const fixture = TestBed.createComponent(App);
|
|
fixture.detectChanges();
|
|
|
|
expect(events).toEqual([
|
|
'app afterContentInit',
|
|
'app afterContentChecked',
|
|
'comp afterContentInit',
|
|
'comp afterContentChecked',
|
|
]);
|
|
});
|
|
});
|
|
|
|
describe('afterViewInit', () => {
|
|
it('should be called on creation and not in update mode', () => {
|
|
let afterViewInitCalls = 0;
|
|
|
|
@Component({
|
|
selector: 'comp',
|
|
template: `<p>test</p>`,
|
|
})
|
|
class Comp {
|
|
ngAfterViewInit() { afterViewInitCalls++; }
|
|
}
|
|
|
|
@Component({template: `<comp></comp>`})
|
|
class App {
|
|
}
|
|
|
|
TestBed.configureTestingModule({
|
|
declarations: [App, Comp],
|
|
});
|
|
const fixture = TestBed.createComponent(App);
|
|
fixture.detectChanges();
|
|
|
|
// two updates
|
|
fixture.detectChanges();
|
|
fixture.detectChanges();
|
|
|
|
expect(afterViewInitCalls).toBe(1);
|
|
|
|
});
|
|
|
|
it('should be called on root component in creation mode', () => {
|
|
let afterViewInitCalls = 0;
|
|
|
|
@Component({template: `<p>test</p>`})
|
|
class App {
|
|
ngAfterViewInit() { afterViewInitCalls++; }
|
|
}
|
|
|
|
TestBed.configureTestingModule({
|
|
declarations: [App],
|
|
});
|
|
const fixture = TestBed.createComponent(App);
|
|
fixture.detectChanges();
|
|
|
|
// two updates
|
|
fixture.detectChanges();
|
|
fixture.detectChanges();
|
|
|
|
expect(afterViewInitCalls).toBe(1);
|
|
});
|
|
|
|
it('should be called every time a view is initialized with ngIf', () => {
|
|
const events: string[] = [];
|
|
|
|
@Component({
|
|
selector: 'comp',
|
|
template: `<p>test</p>`,
|
|
})
|
|
class Comp {
|
|
ngAfterViewInit() { events.push('comp'); }
|
|
}
|
|
|
|
@Component({
|
|
template: `<comp *ngIf="show"></comp>`,
|
|
})
|
|
class App {
|
|
show = true;
|
|
|
|
ngAfterViewInit() { events.push('app'); }
|
|
}
|
|
|
|
TestBed.configureTestingModule({
|
|
declarations: [App, Comp],
|
|
});
|
|
const fixture = TestBed.createComponent(App);
|
|
fixture.detectChanges();
|
|
|
|
expect(events).toEqual(['comp', 'app']);
|
|
|
|
fixture.componentInstance.show = false;
|
|
fixture.detectChanges();
|
|
|
|
expect(events).toEqual(['comp', 'app']);
|
|
|
|
fixture.componentInstance.show = true;
|
|
fixture.detectChanges();
|
|
|
|
expect(events).toEqual(['comp', 'app', 'comp']);
|
|
});
|
|
|
|
it('should be called in children before parents', () => {
|
|
const events: string[] = [];
|
|
|
|
@Component({
|
|
selector: 'parent',
|
|
template: `<child [name]=name></child>`,
|
|
})
|
|
class Parent {
|
|
@Input()
|
|
name = '';
|
|
|
|
ngAfterViewInit() { events.push('parent ' + this.name); }
|
|
}
|
|
|
|
@Component({
|
|
selector: 'child',
|
|
template: `<p>test</p>`,
|
|
})
|
|
class Child {
|
|
@Input()
|
|
name = '';
|
|
|
|
ngAfterViewInit() { events.push('child of parent ' + this.name); }
|
|
}
|
|
|
|
@Component({
|
|
template: `
|
|
<parent name="1"></parent>
|
|
<parent name="2"></parent>
|
|
`
|
|
})
|
|
class App {
|
|
ngAfterViewInit() { events.push('app'); }
|
|
}
|
|
|
|
TestBed.configureTestingModule({
|
|
declarations: [App, Parent, Child],
|
|
});
|
|
const fixture = TestBed.createComponent(App);
|
|
fixture.detectChanges();
|
|
|
|
expect(events).toEqual([
|
|
'child of parent 1',
|
|
'child of parent 2',
|
|
'parent 1',
|
|
'parent 2',
|
|
'app',
|
|
]);
|
|
});
|
|
|
|
it('should be called in projected components before their hosts', () => {
|
|
const events: string[] = [];
|
|
|
|
@Component({
|
|
selector: 'projected',
|
|
template: `<p>test</p>`,
|
|
})
|
|
class Projected {
|
|
@Input()
|
|
name = '';
|
|
|
|
ngAfterViewInit() { events.push('projected ' + this.name); }
|
|
}
|
|
|
|
@Component({
|
|
selector: 'comp',
|
|
template: `<ng-content></ng-content>`,
|
|
})
|
|
class Comp {
|
|
@Input()
|
|
name = '';
|
|
|
|
ngAfterViewInit() { events.push('comp ' + this.name); }
|
|
}
|
|
|
|
@Component({
|
|
template: `
|
|
<comp name="1"><projected name="1"></projected></comp>
|
|
<comp name="2"><projected name="2"></projected></comp>
|
|
`
|
|
})
|
|
class App {
|
|
ngAfterViewInit() { events.push('app'); }
|
|
}
|
|
|
|
TestBed.configureTestingModule({
|
|
declarations: [App, Comp, Projected],
|
|
});
|
|
const fixture = TestBed.createComponent(App);
|
|
fixture.detectChanges();
|
|
|
|
|
|
expect(events).toEqual([
|
|
'projected 1',
|
|
'comp 1',
|
|
'projected 2',
|
|
'comp 2',
|
|
'app',
|
|
]);
|
|
});
|
|
|
|
it('should call afterViewInit in content children and host before next host', () => {
|
|
const events: string[] = [];
|
|
|
|
@Component({
|
|
selector: 'projected-child',
|
|
template: `<p>test</p>`,
|
|
})
|
|
class ProjectedChild {
|
|
@Input()
|
|
name = '';
|
|
|
|
ngAfterViewInit() { events.push('child of projected ' + this.name); }
|
|
}
|
|
|
|
@Component({
|
|
selector: 'projected',
|
|
template: `<projected-child [name]="name"></projected-child>`,
|
|
})
|
|
class Projected {
|
|
@Input()
|
|
name = '';
|
|
|
|
ngAfterViewInit() { events.push('projected ' + this.name); }
|
|
}
|
|
|
|
@Component({
|
|
selector: 'comp',
|
|
template: `<div><ng-content></ng-content></div>`,
|
|
})
|
|
class Comp {
|
|
@Input()
|
|
name = '';
|
|
|
|
ngAfterViewInit() { events.push('comp ' + this.name); }
|
|
}
|
|
|
|
@Component({
|
|
template: `
|
|
<comp name="1"><projected name="1"></projected></comp>
|
|
<comp name="2"><projected name="2"></projected></comp>
|
|
`
|
|
})
|
|
class App {
|
|
ngAfterViewInit() { events.push('app'); }
|
|
}
|
|
|
|
TestBed.configureTestingModule({
|
|
declarations: [App, Comp, Projected, ProjectedChild],
|
|
});
|
|
const fixture = TestBed.createComponent(App);
|
|
fixture.detectChanges();
|
|
|
|
expect(events).toEqual([
|
|
'child of projected 1',
|
|
'child of projected 2',
|
|
'projected 1',
|
|
'comp 1',
|
|
'projected 2',
|
|
'comp 2',
|
|
'app',
|
|
]);
|
|
});
|
|
|
|
it('should be called in correct order with ngFor', () => {
|
|
const events: string[] = [];
|
|
|
|
@Component({
|
|
selector: 'comp',
|
|
template: `<p>test</p>`,
|
|
})
|
|
class Comp {
|
|
@Input()
|
|
name = '';
|
|
|
|
ngAfterViewInit() { events.push('comp ' + this.name); }
|
|
}
|
|
|
|
@Component({
|
|
template: `
|
|
<comp name="4"></comp>
|
|
<comp *ngFor="let number of numbers" [name]="number"></comp>
|
|
<comp name="5"></comp>
|
|
`
|
|
})
|
|
class App {
|
|
numbers = [0, 1, 2, 3];
|
|
|
|
ngAfterViewInit() { events.push('app'); }
|
|
}
|
|
|
|
TestBed.configureTestingModule({
|
|
declarations: [App, Comp],
|
|
imports: [CommonModule],
|
|
});
|
|
const fixture = TestBed.createComponent(App);
|
|
fixture.detectChanges();
|
|
|
|
expect(events).toEqual([
|
|
'comp 0',
|
|
'comp 1',
|
|
'comp 2',
|
|
'comp 3',
|
|
'comp 4',
|
|
'comp 5',
|
|
'app',
|
|
]);
|
|
});
|
|
|
|
it('should be called in correct order with for loops with children', () => {
|
|
const events: string[] = [];
|
|
|
|
@Component({
|
|
selector: 'child',
|
|
template: `<p>test</p>`,
|
|
})
|
|
class Child {
|
|
@Input()
|
|
name = '';
|
|
|
|
ngAfterViewInit() { events.push('child of parent ' + this.name); }
|
|
}
|
|
@Component({
|
|
selector: 'parent',
|
|
template: `<child [name]="name"></child>`,
|
|
})
|
|
class Parent {
|
|
@Input()
|
|
name = '';
|
|
|
|
ngAfterViewInit() { events.push('parent ' + this.name); }
|
|
}
|
|
|
|
@Component({
|
|
template: `
|
|
<parent name="4"></parent>
|
|
<parent *ngFor="let number of numbers" [name]="number"></parent>
|
|
<parent name="5"></parent>
|
|
`
|
|
})
|
|
class App {
|
|
numbers = [0, 1, 2, 3];
|
|
|
|
ngAfterViewInit() { events.push('app'); }
|
|
}
|
|
|
|
TestBed.configureTestingModule({
|
|
declarations: [App, Parent, Child],
|
|
imports: [CommonModule],
|
|
});
|
|
const fixture = TestBed.createComponent(App);
|
|
fixture.detectChanges();
|
|
|
|
expect(events).toEqual([
|
|
'child of parent 0',
|
|
'parent 0',
|
|
'child of parent 1',
|
|
'parent 1',
|
|
'child of parent 2',
|
|
'parent 2',
|
|
'child of parent 3',
|
|
'parent 3',
|
|
'child of parent 4',
|
|
'child of parent 5',
|
|
'parent 4',
|
|
'parent 5',
|
|
'app',
|
|
]);
|
|
});
|
|
|
|
it('should be called on directives after component', () => {
|
|
const events: string[] = [];
|
|
|
|
@Directive({
|
|
selector: '[dir]',
|
|
})
|
|
class Dir {
|
|
@Input('dir')
|
|
name = '';
|
|
|
|
ngAfterViewInit() { events.push('dir ' + this.name); }
|
|
}
|
|
|
|
@Component({
|
|
selector: 'comp',
|
|
template: `<p>test</p>`,
|
|
})
|
|
class Comp {
|
|
@Input()
|
|
name = '';
|
|
|
|
ngAfterViewInit() { events.push('comp ' + this.name); }
|
|
}
|
|
|
|
@Component({
|
|
template: `
|
|
<comp name="1" dir="1"></comp>
|
|
<comp name="2" dir="2"></comp>
|
|
`
|
|
})
|
|
class App {
|
|
ngAfterViewInit() { events.push('app'); }
|
|
}
|
|
|
|
TestBed.configureTestingModule({
|
|
declarations: [App, Comp, Dir],
|
|
});
|
|
const fixture = TestBed.createComponent(App);
|
|
fixture.detectChanges();
|
|
|
|
expect(events).toEqual([
|
|
'comp 1',
|
|
'dir 1',
|
|
'comp 2',
|
|
'dir 2',
|
|
'app',
|
|
]);
|
|
});
|
|
|
|
it('should be called on directives on an element', () => {
|
|
const events: string[] = [];
|
|
|
|
@Directive({
|
|
selector: '[dir]',
|
|
})
|
|
class Dir {
|
|
@Input('dir')
|
|
name = '';
|
|
|
|
ngAfterViewInit() { events.push('dir ' + this.name); }
|
|
}
|
|
|
|
@Component({
|
|
template: `
|
|
<div dir="1"></div>
|
|
<div dir="2"></div>
|
|
`
|
|
})
|
|
class App {
|
|
ngAfterViewInit() { events.push('app'); }
|
|
}
|
|
|
|
TestBed.configureTestingModule({
|
|
declarations: [App, Dir],
|
|
});
|
|
const fixture = TestBed.createComponent(App);
|
|
fixture.detectChanges();
|
|
|
|
expect(events).toEqual([
|
|
'dir 1',
|
|
'dir 2',
|
|
'app',
|
|
]);
|
|
});
|
|
});
|
|
|
|
describe('afterViewChecked', () => {
|
|
it('should call ngAfterViewChecked every update', () => {
|
|
let afterViewCheckedCalls = 0;
|
|
|
|
@Component({
|
|
selector: 'comp',
|
|
template: `<p>test</p>`,
|
|
})
|
|
class Comp {
|
|
ngAfterViewChecked() { afterViewCheckedCalls++; }
|
|
}
|
|
|
|
@Component({template: `<comp></comp>`})
|
|
class App {
|
|
}
|
|
|
|
TestBed.configureTestingModule({
|
|
declarations: [App, Comp],
|
|
});
|
|
const fixture = TestBed.createComponent(App);
|
|
|
|
fixture.detectChanges();
|
|
expect(afterViewCheckedCalls).toBe(1);
|
|
|
|
fixture.detectChanges();
|
|
expect(afterViewCheckedCalls).toBe(2);
|
|
|
|
fixture.detectChanges();
|
|
expect(afterViewCheckedCalls).toBe(3);
|
|
});
|
|
|
|
it('should be called on root component', () => {
|
|
let afterViewCheckedCalls = 0;
|
|
|
|
@Component({template: `<p>test</p>`})
|
|
class App {
|
|
ngAfterViewChecked() { afterViewCheckedCalls++; }
|
|
}
|
|
|
|
TestBed.configureTestingModule({
|
|
declarations: [App],
|
|
});
|
|
const fixture = TestBed.createComponent(App);
|
|
|
|
fixture.detectChanges();
|
|
expect(afterViewCheckedCalls).toBe(1);
|
|
|
|
fixture.detectChanges();
|
|
expect(afterViewCheckedCalls).toBe(2);
|
|
|
|
fixture.detectChanges();
|
|
expect(afterViewCheckedCalls).toBe(3);
|
|
});
|
|
|
|
it('should call ngAfterViewChecked with bindings', () => {
|
|
let afterViewCheckedCalls = 0;
|
|
|
|
@Component({
|
|
selector: 'comp',
|
|
template: `<p>{{value}}</p>`,
|
|
})
|
|
class Comp {
|
|
@Input()
|
|
value = '';
|
|
ngAfterViewChecked() { afterViewCheckedCalls++; }
|
|
}
|
|
|
|
@Component({template: `<comp [value]="value"></comp>`})
|
|
class App {
|
|
value = 1;
|
|
}
|
|
|
|
TestBed.configureTestingModule({
|
|
declarations: [App, Comp],
|
|
});
|
|
const fixture = TestBed.createComponent(App);
|
|
fixture.detectChanges();
|
|
expect(afterViewCheckedCalls).toBe(1);
|
|
|
|
fixture.componentInstance.value = 1337;
|
|
fixture.detectChanges();
|
|
expect(afterViewCheckedCalls).toBe(2);
|
|
});
|
|
|
|
it('should be called in correct order with for loops with children', () => {
|
|
const events: string[] = [];
|
|
|
|
@Component({
|
|
selector: 'child',
|
|
template: `<p>test</p>`,
|
|
})
|
|
class Child {
|
|
@Input()
|
|
name = '';
|
|
|
|
ngAfterViewChecked() { events.push('child of parent ' + this.name); }
|
|
}
|
|
|
|
@Component({
|
|
selector: 'parent',
|
|
template: `<child [name]="name"></child>`,
|
|
})
|
|
class Parent {
|
|
@Input()
|
|
name = '';
|
|
|
|
ngAfterViewChecked() { events.push('parent ' + this.name); }
|
|
}
|
|
|
|
@Component({
|
|
template: `
|
|
<parent name="4"></parent>
|
|
<parent *ngFor="let number of numbers" [name]="number"></parent>
|
|
<parent name="5"></parent>
|
|
`
|
|
})
|
|
class App {
|
|
numbers = [0, 1, 2, 3];
|
|
|
|
ngAfterViewChecked() { events.push('app'); }
|
|
}
|
|
|
|
TestBed.configureTestingModule({
|
|
declarations: [App, Parent, Child],
|
|
});
|
|
const fixture = TestBed.createComponent(App);
|
|
fixture.detectChanges();
|
|
|
|
expect(events).toEqual([
|
|
'child of parent 0',
|
|
'parent 0',
|
|
'child of parent 1',
|
|
'parent 1',
|
|
'child of parent 2',
|
|
'parent 2',
|
|
'child of parent 3',
|
|
'parent 3',
|
|
'child of parent 4',
|
|
'child of parent 5',
|
|
'parent 4',
|
|
'parent 5',
|
|
'app',
|
|
]);
|
|
});
|
|
|
|
it('should be called on directives after component', () => {
|
|
const events: string[] = [];
|
|
|
|
@Directive({
|
|
selector: '[dir]',
|
|
})
|
|
class Dir {
|
|
@Input('dir')
|
|
name = '';
|
|
|
|
ngAfterViewChecked() { events.push('dir ' + this.name); }
|
|
}
|
|
|
|
@Component({
|
|
selector: 'comp',
|
|
template: `<p>test</p>`,
|
|
})
|
|
class Comp {
|
|
@Input()
|
|
name = '';
|
|
|
|
ngAfterViewChecked() { events.push('comp ' + this.name); }
|
|
}
|
|
|
|
@Component({
|
|
template: `
|
|
<comp name="1" dir="1"></comp>
|
|
<comp name="2" dir="2"></comp>
|
|
`
|
|
})
|
|
class App {
|
|
ngAfterViewChecked() { events.push('app'); }
|
|
}
|
|
|
|
TestBed.configureTestingModule({
|
|
declarations: [App, Comp, Dir],
|
|
});
|
|
const fixture = TestBed.createComponent(App);
|
|
fixture.detectChanges();
|
|
|
|
expect(events).toEqual([
|
|
'comp 1',
|
|
'dir 1',
|
|
'comp 2',
|
|
'dir 2',
|
|
'app',
|
|
]);
|
|
});
|
|
|
|
it('should be called on directives on an element', () => {
|
|
const events: string[] = [];
|
|
|
|
@Directive({
|
|
selector: '[dir]',
|
|
})
|
|
class Dir {
|
|
@Input('dir')
|
|
name = '';
|
|
|
|
ngAfterViewChecked() { events.push('dir ' + this.name); }
|
|
}
|
|
|
|
@Component({
|
|
template: `
|
|
<div dir="1"></div>
|
|
<div dir="2"></div>
|
|
`
|
|
})
|
|
class App {
|
|
ngAfterViewChecked() { events.push('app'); }
|
|
}
|
|
|
|
TestBed.configureTestingModule({
|
|
declarations: [App, Dir],
|
|
});
|
|
const fixture = TestBed.createComponent(App);
|
|
fixture.detectChanges();
|
|
|
|
expect(events).toEqual([
|
|
'dir 1',
|
|
'dir 2',
|
|
'app',
|
|
]);
|
|
});
|
|
|
|
});
|
|
|
|
describe('onDestroy', () => {
|
|
|
|
|
|
it('should call destroy when view is removed', () => {
|
|
let destroyCalled = 0;
|
|
|
|
@Component({
|
|
selector: 'comp',
|
|
template: `<p>test</p>`,
|
|
})
|
|
class Comp {
|
|
ngOnDestroy() { destroyCalled++; }
|
|
}
|
|
|
|
@Component({
|
|
template: `<comp *ngIf="show"></comp>`,
|
|
})
|
|
class App {
|
|
show = true;
|
|
}
|
|
|
|
TestBed.configureTestingModule({
|
|
declarations: [App, Comp],
|
|
imports: [CommonModule],
|
|
});
|
|
const fixture = TestBed.createComponent(App);
|
|
fixture.detectChanges();
|
|
|
|
expect(destroyCalled).toBe(0);
|
|
|
|
fixture.componentInstance.show = false;
|
|
fixture.detectChanges();
|
|
|
|
expect(destroyCalled).toBe(1);
|
|
|
|
fixture.componentInstance.show = true;
|
|
fixture.detectChanges();
|
|
|
|
expect(destroyCalled).toBe(1);
|
|
|
|
fixture.componentInstance.show = false;
|
|
fixture.detectChanges();
|
|
|
|
expect(destroyCalled).toBe(2);
|
|
});
|
|
|
|
it('should call destroy when multiple views are removed', () => {
|
|
const events: string[] = [];
|
|
|
|
@Component({
|
|
selector: 'comp',
|
|
template: `<p>test</p>`,
|
|
})
|
|
class Comp {
|
|
@Input()
|
|
name = '';
|
|
|
|
ngOnDestroy() { events.push('comp ' + this.name); }
|
|
}
|
|
|
|
@Component({
|
|
template: `
|
|
<div *ngIf="show">
|
|
<comp name="1"></comp>
|
|
<comp name="2"></comp>
|
|
</div>
|
|
`
|
|
})
|
|
class App {
|
|
show = true;
|
|
}
|
|
|
|
TestBed.configureTestingModule({
|
|
declarations: [App, Comp],
|
|
imports: [CommonModule],
|
|
});
|
|
const fixture = TestBed.createComponent(App);
|
|
fixture.detectChanges();
|
|
|
|
expect(events).toEqual([]);
|
|
|
|
fixture.componentInstance.show = false;
|
|
fixture.detectChanges();
|
|
|
|
expect(events).toEqual(['comp 1', 'comp 2']);
|
|
});
|
|
|
|
it('should be called in child components before parent components', () => {
|
|
const events: string[] = [];
|
|
|
|
@Component({
|
|
selector: 'child',
|
|
template: `<p>test</p>`,
|
|
})
|
|
class Child {
|
|
@Input()
|
|
name = '';
|
|
|
|
ngOnDestroy() { events.push('child of parent ' + this.name); }
|
|
}
|
|
|
|
@Component({
|
|
selector: 'parent',
|
|
template: `<child [name]="name"></child>`,
|
|
})
|
|
class Parent {
|
|
@Input()
|
|
name = '';
|
|
ngOnDestroy() { events.push('parent ' + this.name); }
|
|
}
|
|
|
|
@Component({
|
|
template: `
|
|
<div *ngIf="show">
|
|
<parent name="1"></parent>
|
|
<parent name="2"></parent>
|
|
</div>
|
|
`
|
|
})
|
|
class App {
|
|
show = true;
|
|
}
|
|
|
|
TestBed.configureTestingModule({
|
|
declarations: [App, Parent, Child],
|
|
imports: [CommonModule],
|
|
});
|
|
const fixture = TestBed.createComponent(App);
|
|
fixture.detectChanges();
|
|
|
|
expect(events).toEqual([]);
|
|
|
|
fixture.componentInstance.show = false;
|
|
fixture.detectChanges();
|
|
|
|
expect(events).toEqual([
|
|
'child of parent 1',
|
|
'child of parent 2',
|
|
'parent 1',
|
|
'parent 2',
|
|
]);
|
|
});
|
|
|
|
it('should be called bottom up with children nested 2 levels deep', () => {
|
|
const events: string[] = [];
|
|
|
|
@Component({
|
|
selector: 'child',
|
|
template: `<p>test</p>`,
|
|
})
|
|
class Child {
|
|
@Input()
|
|
name = '';
|
|
|
|
ngOnDestroy() { events.push('child ' + this.name); }
|
|
}
|
|
|
|
@Component({
|
|
selector: 'parent',
|
|
template: `<child [name]="name"></child>`,
|
|
})
|
|
class Parent {
|
|
@Input()
|
|
name = '';
|
|
ngOnDestroy() { events.push('parent ' + this.name); }
|
|
}
|
|
|
|
@Component({
|
|
selector: 'grandparent',
|
|
template: `<parent [name]="name"></parent>`,
|
|
})
|
|
class Grandparent {
|
|
@Input()
|
|
name = '';
|
|
|
|
ngOnDestroy() { events.push('grandparent ' + this.name); }
|
|
}
|
|
@Component({
|
|
template: `
|
|
<div *ngIf="show">
|
|
<grandparent name="1"></grandparent>
|
|
<grandparent name="2"></grandparent>
|
|
</div>
|
|
`
|
|
})
|
|
class App {
|
|
show = true;
|
|
}
|
|
|
|
TestBed.configureTestingModule({
|
|
declarations: [App, Grandparent, Parent, Child],
|
|
imports: [CommonModule],
|
|
});
|
|
const fixture = TestBed.createComponent(App);
|
|
fixture.detectChanges();
|
|
|
|
expect(events).toEqual([]);
|
|
|
|
fixture.componentInstance.show = false;
|
|
fixture.detectChanges();
|
|
|
|
expect(events).toEqual([
|
|
'child 1',
|
|
'parent 1',
|
|
'child 2',
|
|
'parent 2',
|
|
'grandparent 1',
|
|
'grandparent 2',
|
|
]);
|
|
});
|
|
|
|
it('should be called in projected components before their hosts', () => {
|
|
const events: string[] = [];
|
|
|
|
@Component({
|
|
selector: 'projected',
|
|
template: `<p>test</p>`,
|
|
})
|
|
class Projected {
|
|
@Input()
|
|
name = '';
|
|
|
|
ngOnDestroy() { events.push('projected ' + this.name); }
|
|
}
|
|
|
|
@Component({
|
|
selector: 'comp',
|
|
template: `<div><ng-content></ng-content></div>`,
|
|
})
|
|
class Comp {
|
|
@Input()
|
|
name = '';
|
|
|
|
ngOnDestroy() { events.push('comp ' + this.name); }
|
|
}
|
|
|
|
@Component({
|
|
template: `
|
|
<div *ngIf="show">
|
|
<comp name="1">
|
|
<projected name="1"></projected>
|
|
</comp>
|
|
<comp name="2">
|
|
<projected name="2"></projected>
|
|
</comp>
|
|
</div>
|
|
`
|
|
})
|
|
class App {
|
|
show = true;
|
|
}
|
|
|
|
TestBed.configureTestingModule({
|
|
declarations: [App, Comp, Projected],
|
|
});
|
|
const fixture = TestBed.createComponent(App);
|
|
fixture.detectChanges();
|
|
|
|
expect(events).toEqual([]);
|
|
|
|
fixture.componentInstance.show = false;
|
|
fixture.detectChanges();
|
|
|
|
expect(events).toEqual([
|
|
'projected 1',
|
|
'comp 1',
|
|
'projected 2',
|
|
'comp 2',
|
|
]);
|
|
});
|
|
|
|
|
|
it('should be called in consistent order if views are removed and re-added', () => {
|
|
const events: string[] = [];
|
|
|
|
@Component({
|
|
selector: 'comp',
|
|
template: `<p>test</p>`,
|
|
})
|
|
class Comp {
|
|
@Input()
|
|
name = '';
|
|
|
|
ngOnDestroy() { events.push('comp ' + this.name); }
|
|
}
|
|
|
|
@Component({
|
|
template: `
|
|
<div *ngIf="showAll">
|
|
<comp name="1"></comp>
|
|
<comp *ngIf="showMiddle" name="2"></comp>
|
|
<comp name="3"></comp>
|
|
</div>
|
|
`
|
|
})
|
|
class App {
|
|
showAll = true;
|
|
showMiddle = true;
|
|
}
|
|
|
|
TestBed.configureTestingModule({
|
|
declarations: [App, Comp],
|
|
imports: [CommonModule],
|
|
});
|
|
const fixture = TestBed.createComponent(App);
|
|
fixture.detectChanges();
|
|
|
|
expect(events).toEqual([]);
|
|
|
|
fixture.componentInstance.showMiddle = false;
|
|
fixture.detectChanges();
|
|
expect(events).toEqual(['comp 2']);
|
|
|
|
fixture.componentInstance.showAll = false;
|
|
fixture.detectChanges();
|
|
expect(events).toEqual([
|
|
'comp 2',
|
|
'comp 1',
|
|
'comp 3',
|
|
]);
|
|
|
|
fixture.componentInstance.showAll = true;
|
|
fixture.componentInstance.showMiddle = true;
|
|
fixture.detectChanges();
|
|
expect(events).toEqual([
|
|
'comp 2',
|
|
'comp 1',
|
|
'comp 3',
|
|
]);
|
|
|
|
fixture.componentInstance.showAll = false;
|
|
fixture.detectChanges();
|
|
expect(events).toEqual([
|
|
'comp 2',
|
|
'comp 1',
|
|
'comp 3',
|
|
'comp 2',
|
|
'comp 1',
|
|
'comp 3',
|
|
]);
|
|
});
|
|
|
|
it('should be called on every iteration of a destroyed for loop', () => {
|
|
const events: string[] = [];
|
|
|
|
@Component({
|
|
selector: 'comp',
|
|
template: `<p>test</p>`,
|
|
})
|
|
class Comp {
|
|
@Input()
|
|
name = '';
|
|
|
|
ngOnDestroy() { events.push('comp ' + this.name); }
|
|
}
|
|
|
|
@Component({
|
|
template: `
|
|
<div *ngIf="show">
|
|
<comp *ngFor="let number of numbers" [name]="number"></comp>
|
|
</div>
|
|
`
|
|
})
|
|
class App {
|
|
show = true;
|
|
numbers = [0, 1, 2, 3];
|
|
}
|
|
|
|
TestBed.configureTestingModule({
|
|
declarations: [App, Comp],
|
|
});
|
|
const fixture = TestBed.createComponent(App);
|
|
fixture.detectChanges();
|
|
|
|
expect(events).toEqual([]);
|
|
|
|
fixture.componentInstance.show = false;
|
|
fixture.detectChanges();
|
|
|
|
expect(events).toEqual([
|
|
'comp 0',
|
|
'comp 1',
|
|
'comp 2',
|
|
'comp 3',
|
|
]);
|
|
|
|
fixture.componentInstance.show = true;
|
|
fixture.detectChanges();
|
|
|
|
expect(events).toEqual([
|
|
'comp 0',
|
|
'comp 1',
|
|
'comp 2',
|
|
'comp 3',
|
|
]);
|
|
|
|
fixture.componentInstance.numbers.splice(1, 1);
|
|
fixture.detectChanges();
|
|
expect(events).toEqual([
|
|
'comp 0',
|
|
'comp 1',
|
|
'comp 2',
|
|
'comp 3',
|
|
'comp 1',
|
|
]);
|
|
|
|
fixture.componentInstance.show = false;
|
|
fixture.detectChanges();
|
|
expect(events).toEqual([
|
|
'comp 0',
|
|
'comp 1',
|
|
'comp 2',
|
|
'comp 3',
|
|
'comp 1',
|
|
'comp 0',
|
|
'comp 2',
|
|
'comp 3',
|
|
]);
|
|
});
|
|
|
|
it('should call destroy properly if view also has listeners', () => {
|
|
const events: string[] = [];
|
|
|
|
@Component({
|
|
selector: 'comp',
|
|
template: `<p>test</p>`,
|
|
})
|
|
class Comp {
|
|
ngOnDestroy() { events.push('comp'); }
|
|
}
|
|
@Component({
|
|
template: `
|
|
<div *ngIf="show">
|
|
<button (click)="handleClick1()">test 1</button>
|
|
<comp></comp>
|
|
<button (click)="handleClick2()">test 2</button>
|
|
</div>
|
|
`
|
|
})
|
|
class App {
|
|
show = true;
|
|
|
|
clicksToButton1 = 0;
|
|
|
|
clicksToButton2 = 0;
|
|
|
|
handleClick1() { this.clicksToButton1++; }
|
|
|
|
handleClick2() { this.clicksToButton2++; }
|
|
}
|
|
|
|
TestBed.configureTestingModule({
|
|
declarations: [App, Comp],
|
|
});
|
|
const fixture = TestBed.createComponent(App);
|
|
fixture.detectChanges();
|
|
|
|
const buttons = fixture.debugElement.queryAll(By.css('button'));
|
|
buttons.forEach(button => button.nativeElement.click());
|
|
|
|
expect(fixture.componentInstance.clicksToButton1).toBe(1);
|
|
expect(fixture.componentInstance.clicksToButton2).toBe(1);
|
|
expect(events).toEqual([]);
|
|
|
|
fixture.componentInstance.show = false;
|
|
fixture.detectChanges();
|
|
|
|
buttons.forEach(button => button.nativeElement.click());
|
|
expect(fixture.componentInstance.clicksToButton1).toBe(1);
|
|
expect(fixture.componentInstance.clicksToButton2).toBe(1);
|
|
|
|
expect(events).toEqual(['comp']);
|
|
});
|
|
|
|
onlyInIvy(
|
|
'View Engine has the opposite behavior, where it calls destroy on the directives first, then the components')
|
|
.it('should be called on directives after component', () => {
|
|
const events: string[] = [];
|
|
|
|
@Directive({
|
|
selector: '[dir]',
|
|
})
|
|
class Dir {
|
|
@Input('dir')
|
|
name = '';
|
|
|
|
ngOnDestroy() { events.push('dir ' + this.name); }
|
|
}
|
|
|
|
@Component({
|
|
selector: 'comp',
|
|
template: `<p>test</p>`,
|
|
})
|
|
class Comp {
|
|
@Input()
|
|
name = '';
|
|
|
|
ngOnDestroy() { events.push('comp ' + this.name); }
|
|
}
|
|
|
|
@Component({
|
|
template: `
|
|
<div *ngIf="show">
|
|
<comp name="1" dir="1"></comp>
|
|
<comp name="2" dir="2"></comp>
|
|
</div>
|
|
`
|
|
})
|
|
class App {
|
|
show = true;
|
|
}
|
|
|
|
TestBed.configureTestingModule({
|
|
declarations: [App, Dir, Comp],
|
|
imports: [CommonModule],
|
|
});
|
|
const fixture = TestBed.createComponent(App);
|
|
fixture.detectChanges();
|
|
|
|
expect(events).toEqual([]);
|
|
|
|
fixture.componentInstance.show = false;
|
|
fixture.detectChanges();
|
|
|
|
expect(events).toEqual([
|
|
'comp 1',
|
|
'dir 1',
|
|
'comp 2',
|
|
'dir 2',
|
|
]);
|
|
});
|
|
|
|
it('should be called on directives on an element', () => {
|
|
const events: string[] = [];
|
|
|
|
@Directive({
|
|
selector: '[dir]',
|
|
})
|
|
class Dir {
|
|
ngOnDestroy() { events.push('dir'); }
|
|
}
|
|
|
|
@Component({template: `<p *ngIf="show" dir></p>`})
|
|
class App {
|
|
show = true;
|
|
}
|
|
|
|
TestBed.configureTestingModule({
|
|
declarations: [App, Dir],
|
|
imports: [CommonModule],
|
|
});
|
|
const fixture = TestBed.createComponent(App);
|
|
fixture.detectChanges();
|
|
|
|
expect(events).toEqual([]);
|
|
|
|
fixture.componentInstance.show = false;
|
|
fixture.detectChanges();
|
|
|
|
expect(events).toEqual(['dir']);
|
|
});
|
|
});
|
|
|
|
describe('hook order', () => {
|
|
let events: string[] = [];
|
|
|
|
beforeEach(() => events = []);
|
|
|
|
@Component({
|
|
selector: 'comp',
|
|
template: `{{value}}<div><ng-content></ng-content></div>`,
|
|
})
|
|
class Comp {
|
|
@Input()
|
|
value = '';
|
|
|
|
@Input()
|
|
name = '';
|
|
|
|
ngOnInit() { events.push(`${this.name} onInit`); }
|
|
|
|
ngDoCheck() { events.push(`${this.name} doCheck`); }
|
|
|
|
ngOnChanges() { events.push(`${this.name} onChanges`); }
|
|
|
|
ngAfterContentInit() { events.push(`${this.name} afterContentInit`); }
|
|
|
|
ngAfterContentChecked() { events.push(`${this.name} afterContentChecked`); }
|
|
|
|
ngAfterViewInit() { events.push(`${this.name} afterViewInit`); }
|
|
|
|
ngAfterViewChecked() { events.push(`${this.name} afterViewChecked`); }
|
|
|
|
ngOnDestroy() { events.push(`${this.name} onDestroy`); }
|
|
}
|
|
|
|
@Component({
|
|
selector: 'parent',
|
|
template:
|
|
`<comp [name]="'child of ' + this.name" [value]="value"><ng-content></ng-content></comp>`,
|
|
})
|
|
class Parent extends Comp {
|
|
}
|
|
|
|
it('should call all hooks in correct order', () => {
|
|
@Component({template: `<comp *ngIf="show" name="comp" [value]="value"></comp>`})
|
|
class App {
|
|
value = 'a';
|
|
|
|
show = true;
|
|
}
|
|
|
|
TestBed.configureTestingModule({
|
|
declarations: [App, Comp],
|
|
imports: [CommonModule],
|
|
});
|
|
const fixture = TestBed.createComponent(App);
|
|
fixture.detectChanges();
|
|
|
|
expect(events).toEqual([
|
|
'comp onChanges',
|
|
'comp onInit',
|
|
'comp doCheck',
|
|
'comp afterContentInit',
|
|
'comp afterContentChecked',
|
|
'comp afterViewInit',
|
|
'comp afterViewChecked',
|
|
]);
|
|
|
|
events.length = 0;
|
|
fixture.detectChanges();
|
|
expect(events).toEqual([
|
|
'comp doCheck',
|
|
'comp afterContentChecked',
|
|
'comp afterViewChecked',
|
|
]);
|
|
|
|
events.length = 0;
|
|
fixture.componentInstance.value = 'b';
|
|
fixture.detectChanges();
|
|
expect(events).toEqual([
|
|
'comp onChanges',
|
|
'comp doCheck',
|
|
'comp afterContentChecked',
|
|
'comp afterViewChecked',
|
|
]);
|
|
|
|
events.length = 0;
|
|
fixture.componentInstance.show = false;
|
|
fixture.detectChanges();
|
|
expect(events).toEqual([
|
|
'comp onDestroy',
|
|
]);
|
|
});
|
|
|
|
it('should call all hooks in correct order with children', () => {
|
|
@Component({
|
|
template: `
|
|
<div *ngIf="show">
|
|
<parent name="parent1" [value]="value"></parent>
|
|
<parent name="parent2" [value]="value"></parent>
|
|
</div>
|
|
`
|
|
})
|
|
class App {
|
|
value = 'a';
|
|
|
|
show = true;
|
|
}
|
|
|
|
TestBed.configureTestingModule({
|
|
declarations: [App, Parent, Comp],
|
|
imports: [CommonModule],
|
|
});
|
|
const fixture = TestBed.createComponent(App);
|
|
fixture.detectChanges();
|
|
|
|
expect(events).toEqual([
|
|
'parent1 onChanges',
|
|
'parent1 onInit',
|
|
'parent1 doCheck',
|
|
'parent2 onChanges',
|
|
'parent2 onInit',
|
|
'parent2 doCheck',
|
|
'parent1 afterContentInit',
|
|
'parent1 afterContentChecked',
|
|
'parent2 afterContentInit',
|
|
'parent2 afterContentChecked',
|
|
'child of parent1 onChanges',
|
|
'child of parent1 onInit',
|
|
'child of parent1 doCheck',
|
|
'child of parent1 afterContentInit',
|
|
'child of parent1 afterContentChecked',
|
|
'child of parent1 afterViewInit',
|
|
'child of parent1 afterViewChecked',
|
|
'child of parent2 onChanges',
|
|
'child of parent2 onInit',
|
|
'child of parent2 doCheck',
|
|
'child of parent2 afterContentInit',
|
|
'child of parent2 afterContentChecked',
|
|
'child of parent2 afterViewInit',
|
|
'child of parent2 afterViewChecked',
|
|
'parent1 afterViewInit',
|
|
'parent1 afterViewChecked',
|
|
'parent2 afterViewInit',
|
|
'parent2 afterViewChecked',
|
|
]);
|
|
|
|
events.length = 0;
|
|
fixture.componentInstance.value = 'b';
|
|
fixture.detectChanges();
|
|
|
|
expect(events).toEqual([
|
|
'parent1 onChanges',
|
|
'parent1 doCheck',
|
|
'parent2 onChanges',
|
|
'parent2 doCheck',
|
|
'parent1 afterContentChecked',
|
|
'parent2 afterContentChecked',
|
|
'child of parent1 onChanges',
|
|
'child of parent1 doCheck',
|
|
'child of parent1 afterContentChecked',
|
|
'child of parent1 afterViewChecked',
|
|
'child of parent2 onChanges',
|
|
'child of parent2 doCheck',
|
|
'child of parent2 afterContentChecked',
|
|
'child of parent2 afterViewChecked',
|
|
'parent1 afterViewChecked',
|
|
'parent2 afterViewChecked',
|
|
]);
|
|
|
|
events.length = 0;
|
|
fixture.componentInstance.show = false;
|
|
fixture.detectChanges();
|
|
|
|
expect(events).toEqual([
|
|
'child of parent1 onDestroy',
|
|
'child of parent2 onDestroy',
|
|
'parent1 onDestroy',
|
|
'parent2 onDestroy',
|
|
]);
|
|
});
|
|
|
|
// Angular 5 reference: https://stackblitz.com/edit/lifecycle-hooks-ng
|
|
it('should call all hooks in correct order with view and content', () => {
|
|
@Component({
|
|
template: `
|
|
<div *ngIf="show">
|
|
<parent name="parent1" [value]="value">
|
|
<comp name="projected1" [value]="value"></comp>
|
|
</parent>
|
|
<parent name="parent2" [value]="value">
|
|
<comp name="projected2" [value]="value"></comp>
|
|
</parent>
|
|
</div>
|
|
`
|
|
})
|
|
class App {
|
|
value = 'a';
|
|
show = true;
|
|
}
|
|
|
|
TestBed.configureTestingModule({
|
|
declarations: [App, Parent, Comp],
|
|
imports: [CommonModule],
|
|
});
|
|
const fixture = TestBed.createComponent(App);
|
|
fixture.detectChanges();
|
|
|
|
expect(events).toEqual([
|
|
'parent1 onChanges',
|
|
'parent1 onInit',
|
|
'parent1 doCheck',
|
|
'projected1 onChanges',
|
|
'projected1 onInit',
|
|
'projected1 doCheck',
|
|
'parent2 onChanges',
|
|
'parent2 onInit',
|
|
'parent2 doCheck',
|
|
'projected2 onChanges',
|
|
'projected2 onInit',
|
|
'projected2 doCheck',
|
|
'projected1 afterContentInit',
|
|
'projected1 afterContentChecked',
|
|
'parent1 afterContentInit',
|
|
'parent1 afterContentChecked',
|
|
'projected2 afterContentInit',
|
|
'projected2 afterContentChecked',
|
|
'parent2 afterContentInit',
|
|
'parent2 afterContentChecked',
|
|
'child of parent1 onChanges',
|
|
'child of parent1 onInit',
|
|
'child of parent1 doCheck',
|
|
'child of parent1 afterContentInit',
|
|
'child of parent1 afterContentChecked',
|
|
'child of parent1 afterViewInit',
|
|
'child of parent1 afterViewChecked',
|
|
'child of parent2 onChanges',
|
|
'child of parent2 onInit',
|
|
'child of parent2 doCheck',
|
|
'child of parent2 afterContentInit',
|
|
'child of parent2 afterContentChecked',
|
|
'child of parent2 afterViewInit',
|
|
'child of parent2 afterViewChecked',
|
|
'projected1 afterViewInit',
|
|
'projected1 afterViewChecked',
|
|
'parent1 afterViewInit',
|
|
'parent1 afterViewChecked',
|
|
'projected2 afterViewInit',
|
|
'projected2 afterViewChecked',
|
|
'parent2 afterViewInit',
|
|
'parent2 afterViewChecked',
|
|
]);
|
|
|
|
events.length = 0;
|
|
fixture.componentInstance.value = 'b';
|
|
fixture.detectChanges();
|
|
|
|
expect(events).toEqual([
|
|
'parent1 onChanges',
|
|
'parent1 doCheck',
|
|
'projected1 onChanges',
|
|
'projected1 doCheck',
|
|
'parent2 onChanges',
|
|
'parent2 doCheck',
|
|
'projected2 onChanges',
|
|
'projected2 doCheck',
|
|
'projected1 afterContentChecked',
|
|
'parent1 afterContentChecked',
|
|
'projected2 afterContentChecked',
|
|
'parent2 afterContentChecked',
|
|
'child of parent1 onChanges',
|
|
'child of parent1 doCheck',
|
|
'child of parent1 afterContentChecked',
|
|
'child of parent1 afterViewChecked',
|
|
'child of parent2 onChanges',
|
|
'child of parent2 doCheck',
|
|
'child of parent2 afterContentChecked',
|
|
'child of parent2 afterViewChecked',
|
|
'projected1 afterViewChecked',
|
|
'parent1 afterViewChecked',
|
|
'projected2 afterViewChecked',
|
|
'parent2 afterViewChecked',
|
|
]);
|
|
|
|
events.length = 0;
|
|
fixture.componentInstance.show = false;
|
|
fixture.detectChanges();
|
|
|
|
expect(events).toEqual([
|
|
'child of parent1 onDestroy',
|
|
'child of parent2 onDestroy',
|
|
'projected1 onDestroy',
|
|
'parent1 onDestroy',
|
|
'projected2 onDestroy',
|
|
'parent2 onDestroy',
|
|
]);
|
|
});
|
|
});
|
|
|
|
describe('non-regression', () => {
|
|
it('should call lifecycle hooks for directives active on <ng-template>', () => {
|
|
let destroyed = false;
|
|
|
|
@Directive({
|
|
selector: '[onDestroyDir]',
|
|
})
|
|
class OnDestroyDir {
|
|
ngOnDestroy() { destroyed = true; }
|
|
}
|
|
|
|
@Component({
|
|
template: `<ng-template [ngIf]="show">
|
|
<ng-template onDestroyDir>content</ng-template>
|
|
</ng-template>`
|
|
})
|
|
class App {
|
|
show = true;
|
|
}
|
|
|
|
TestBed.configureTestingModule({
|
|
declarations: [App, OnDestroyDir],
|
|
});
|
|
const fixture = TestBed.createComponent(App);
|
|
fixture.detectChanges();
|
|
|
|
expect(destroyed).toBeFalsy();
|
|
|
|
fixture.componentInstance.show = false;
|
|
fixture.detectChanges();
|
|
|
|
expect(destroyed).toBeTruthy();
|
|
});
|
|
});
|