diff --git a/packages/core/test/acceptance/lifecycle_spec.ts b/packages/core/test/acceptance/lifecycle_spec.ts
index f5ce6fd7d8..55330ec033 100644
--- a/packages/core/test/acceptance/lifecycle_spec.ts
+++ b/packages/core/test/acceptance/lifecycle_spec.ts
@@ -6,8 +6,10 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {Component, Directive, Input, OnChanges, SimpleChanges} from '@angular/core';
+import {CommonModule} from '@angular/common';
+import {Component, ComponentFactoryResolver, Directive, Input, NgModule, OnChanges, SimpleChanges, ViewChild, ViewContainerRef} from '@angular/core';
import {TestBed} from '@angular/core/testing';
+import {By} from '@angular/platform-browser';
describe('ngOnChanges', () => {
it('should correctly support updating one Input among many', () => {
@@ -169,3 +171,494 @@ it('should call hooks after setting directives inputs', () => {
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: `
test
`,
+ })
+ class MyComponent {
+ @Input()
+ input1 = '';
+
+ @Input()
+ input2 = '';
+
+ ngOnInit() {
+ input1Values.push(this.input1);
+ input2Values.push(this.input2);
+ }
+ }
+
+ @Component({
+ template: `
+
+ `,
+ })
+ 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: `child
`,
+ })
+ class ChildComp {
+ ngOnInit() { initCalls.push('child'); }
+ }
+
+ @Component({
+ template: ``,
+ })
+ 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: `child
`,
+ })
+ class ChildComp {
+ @Input()
+ name = '';
+
+ ngOnInit() { initCalls.push(`child of parent ${this.name}`); }
+ }
+
+ @Component({
+ selector: 'parent-comp',
+ template: ``,
+ })
+ class ParentComp {
+ @Input()
+ name = '';
+
+ ngOnInit() { initCalls.push(`parent ${this.name}`); }
+ }
+
+ @Component({
+ template: `
+
+
+ `
+ })
+ 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: 'test
'})
+ class MyComp {
+ ngOnInit() { onInitCalls++; }
+ }
+
+ @Component({
+ template: `
+
+ `
+ })
+ 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: 'test
'})
+ class MyComp {
+ onInitCalled = false;
+
+ ngOnInit() { this.onInitCalled = true; }
+ }
+
+ @Component({
+ selector: 'dynamic-comp',
+ template: `
+
+ `,
+ })
+ class DynamicComp {
+ }
+
+ @Component({
+ template: `
+
+ `,
+ })
+ 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: ``,
+ })
+ class Comp {
+ ngOnInit() { initialized.push('comp'); }
+ }
+
+ @Component({
+ template: `
+
+
+
+ `
+ })
+ 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: ``,
+ })
+ class Comp {
+ @Input()
+ name = '';
+
+ ngOnInit() { initialized.push('comp ' + this.name); }
+ }
+
+ @Component({
+ template: `
+
+
+
+
+
+
+ `
+ })
+ 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', () => {
+ const initialized: string[] = [];
+
+ @Directive({
+ selector: '[dir]',
+ })
+ class Dir {
+ @Input('dir-name')
+ name = '';
+
+ ngOnInit() { initialized.push('dir ' + this.name); }
+ }
+
+ @Component({
+ selector: 'comp',
+ template: ``,
+ })
+ class Comp {
+ @Input()
+ name = '';
+
+ ngOnInit() { initialized.push('comp ' + this.name); }
+ }
+
+ @Component({
+ template: `
+
+
+ `
+ })
+ 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 directives on an element', () => {
+ const initialized: string[] = [];
+
+ @Directive({
+ selector: '[dir]',
+ })
+ class Dir {
+ @Input('dir-name')
+ name = '';
+
+ ngOnInit() { initialized.push('dir ' + this.name); }
+ }
+
+ @Component({
+ template: `
+
+
+ `
+ })
+ 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: ``,
+ })
+ class Comp {
+ @Input()
+ name = '';
+
+ ngOnInit() { initialized.push('comp ' + this.name); }
+ }
+
+ @Component({
+ template: `
+
+
+
+ `
+ })
+ 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: ``,
+ })
+ class Child {
+ @Input()
+ name = '';
+
+ ngOnInit() { initialized.push('child of parent ' + this.name); }
+ }
+
+ @Component({selector: 'parent', template: ''})
+ class Parent {
+ @Input()
+ name = '';
+
+ ngOnInit() { initialized.push('parent ' + this.name); }
+ }
+
+ @Component({
+ template: `
+
+
+
+ `
+ })
+ 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',
+ ]);
+ });
+});
diff --git a/packages/core/test/render3/lifecycle_spec.ts b/packages/core/test/render3/lifecycle_spec.ts
index c7c9c465c4..868f185bb0 100644
--- a/packages/core/test/render3/lifecycle_spec.ts
+++ b/packages/core/test/render3/lifecycle_spec.ts
@@ -78,75 +78,6 @@ describe('lifecycles', () => {
const directives = [Comp, Parent, ProjectedComp, Directive, NgIf];
- it('should call onInit method after inputs are set in creation mode (and not in update mode)',
- () => {
- /** */
- const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- Δelement(0, 'comp');
- }
- if (rf & RenderFlags.Update) {
- ΔelementProperty(0, 'val', Δbind(ctx.val));
- }
- }, 1, 1, directives);
-
- const fixture = new ComponentFixture(App);
- fixture.update();
- expect(events).toEqual(['comp']);
-
- fixture.component.val = '2';
- fixture.update();
- expect(events).toEqual(['comp']);
- });
-
- it('should be called on root component in creation mode', () => {
- const comp = renderComponent(Comp, {hostFeatures: [LifecycleHooksFeature]});
- expect(events).toEqual(['comp']);
-
- markDirty(comp);
- requestAnimationFrame.flush();
- expect(events).toEqual(['comp']);
- });
-
- it('should call parent onInit before child onInit', () => {
- /**
- *
- * parent temp:
- */
- const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- Δelement(0, 'parent');
- }
- }, 1, 0, directives);
-
- const fixture = new ComponentFixture(App);
- expect(events).toEqual(['parent', 'comp']);
- });
-
- it('should call all parent onInits across view before calling children onInits', () => {
- /**
- *
- *
- *
- * parent temp:
- */
- const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- Δelement(0, 'parent');
- Δelement(1, 'parent');
- }
- if (rf & RenderFlags.Update) {
- ΔelementProperty(0, 'val', 1);
- Δselect(1);
- ΔelementProperty(1, 'val', 2);
- }
- }, 2, 0, directives);
-
- const fixture = new ComponentFixture(App);
- expect(events).toEqual(['parent1', 'parent2', 'comp1', 'comp2']);
- });
-
-
it('should call onInit every time a new view is created (if block)', () => {
/**
* % if (!skip) {
@@ -183,239 +114,6 @@ describe('lifecycles', () => {
fixture.update();
expect(events).toEqual(['comp', 'comp']);
});
-
-
- it('should call onInit every time a new view is created (ngIf)', () => {
-
- function IfTemplate(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- Δelement(0, 'comp');
- }
- }
-
- /** */
- const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- Δtemplate(0, IfTemplate, 1, 0, 'comp', [AttributeMarker.Template, 'ngIf']);
- }
- if (rf & RenderFlags.Update) {
- ΔelementProperty(0, 'ngIf', Δbind(ctx.showing));
- }
- }, 1, 0, directives);
-
- const fixture = new ComponentFixture(App);
-
- fixture.component.showing = true;
- fixture.update();
- expect(events).toEqual(['comp']);
-
- fixture.component.showing = false;
- fixture.update();
- expect(events).toEqual(['comp']);
-
- fixture.component.showing = true;
- fixture.update();
- expect(events).toEqual(['comp', 'comp']);
- });
-
- it('should call onInit for children of dynamically created components', () => {
- let viewContainerComp !: ViewContainerComp;
-
- class ViewContainerComp {
- constructor(public vcr: ViewContainerRef, public cfr: ComponentFactoryResolver) {}
-
- static ngComponentDef = ΔdefineComponent({
- type: ViewContainerComp,
- selectors: [['view-container-comp']],
- factory: () => viewContainerComp = new ViewContainerComp(
- ΔdirectiveInject(ViewContainerRef as any), injectComponentFactoryResolver()),
- consts: 0,
- vars: 0,
- template: (rf: RenderFlags, ctx: ViewContainerComp) => {}
- });
- }
-
- const DynamicComp = createComponent('dynamic-comp', (rf: RenderFlags, ctx: any) => {
- if (rf & RenderFlags.Create) {
- Δelement(0, 'comp');
- }
- }, 1, 0, [Comp]);
-
- const fixture = new ComponentFixture(ViewContainerComp);
- expect(events).toEqual([]);
-
- viewContainerComp.vcr.createComponent(
- viewContainerComp.cfr.resolveComponentFactory(DynamicComp));
- fixture.update();
- expect(events).toEqual(['comp']);
- });
-
- it('should call onInit in hosts before their content children', () => {
- /**
- *
- *
- *
- */
- const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ΔelementStart(0, 'comp');
- { ΔelementStart(1, 'projected'); }
- ΔelementEnd();
- }
- }, 2, 0, directives);
-
- const fixture = new ComponentFixture(App);
- expect(events).toEqual(['comp', 'projected']);
- });
-
- it('should call onInit in host and its content children before next host', () => {
- /**
- *
- *
- *
- *
- *
- *
- */
- const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ΔelementStart(0, 'comp');
- { ΔelementStart(1, 'projected'); }
- ΔelementEnd();
- ΔelementStart(2, 'comp');
- { ΔelementStart(3, 'projected'); }
- ΔelementEnd();
- }
- if (rf & RenderFlags.Update) {
- ΔelementProperty(0, 'val', 1);
- Δselect(1);
- ΔelementProperty(1, 'val', 1);
- Δselect(2);
- ΔelementProperty(2, 'val', 2);
- Δselect(3);
- ΔelementProperty(3, 'val', 2);
- }
- }, 4, 0, directives);
-
- const fixture = new ComponentFixture(App);
- expect(events).toEqual(['comp1', 'projected1', 'comp2', 'projected2']);
- });
-
- it('should be called on directives after component', () => {
- /** */
- const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- Δelement(0, 'comp', ['dir', '']);
- }
- }, 1, 0, directives);
-
- const fixture = new ComponentFixture(App);
- expect(events).toEqual(['comp', 'dir']);
-
- fixture.update();
- expect(events).toEqual(['comp', 'dir']);
- });
-
- it('should be called on directives on an element', () => {
- /** */
- const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- Δelement(0, 'div', ['dir', '']);
- }
- }, 1, 0, directives);
-
- const fixture = new ComponentFixture(App);
- expect(events).toEqual(['dir']);
-
- fixture.update();
- expect(events).toEqual(['dir']);
- });
-
- it('should call onInit properly in for loop', () => {
- /**
- *
- * % for (let j = 2; j < 5; j++) {
- *
- * % }
- *
- */
- const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- Δelement(0, 'comp');
- Δcontainer(1);
- Δelement(2, 'comp');
- }
- if (rf & RenderFlags.Update) {
- ΔelementProperty(0, 'val', 1);
- Δselect(2);
- ΔelementProperty(2, 'val', 5);
- ΔcontainerRefreshStart(1);
- {
- for (let j = 2; j < 5; j++) {
- let rf1 = ΔembeddedViewStart(0, 1, 0);
- if (rf1 & RenderFlags.Create) {
- Δelement(0, 'comp');
- }
- if (rf1 & RenderFlags.Update) {
- ΔelementProperty(0, 'val', j);
- }
- ΔembeddedViewEnd();
- }
- }
- ΔcontainerRefreshEnd();
- }
- }, 3, 0, directives);
-
- const fixture = new ComponentFixture(App);
- // onInit is called top to bottom, so top level comps (1 and 5) are called
- // before the comps inside the for loop's embedded view (2, 3, and 4)
- expect(events).toEqual(['comp1', 'comp5', 'comp2', 'comp3', 'comp4']);
- });
-
- it('should call onInit properly in for loop with children', () => {
- /**
- *
- * % for (let j = 2; j < 5; j++) {
- *
- * % }
- *
- */
- const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- Δelement(0, 'parent');
- Δcontainer(1);
- Δelement(2, 'parent');
- }
- if (rf & RenderFlags.Update) {
- ΔelementProperty(0, 'val', 1);
- Δselect(2);
- ΔelementProperty(2, 'val', 5);
- ΔcontainerRefreshStart(1);
- {
- for (let j = 2; j < 5; j++) {
- let rf1 = ΔembeddedViewStart(0, 1, 0);
- if (rf1 & RenderFlags.Create) {
- Δelement(0, 'parent');
- }
- if (rf1 & RenderFlags.Update) {
- ΔelementProperty(0, 'val', j);
- }
- ΔembeddedViewEnd();
- }
- }
- ΔcontainerRefreshEnd();
- }
- }, 3, 0, directives);
-
- const fixture = new ComponentFixture(App);
- // onInit is called top to bottom, so top level comps (1 and 5) are called
- // before the comps inside the for loop's embedded view (2, 3, and 4)
- expect(events).toEqual([
- 'parent1', 'parent5', 'parent2', 'comp2', 'parent3', 'comp3', 'parent4', 'comp4', 'comp1',
- 'comp5'
- ]);
- });
-
});
describe('doCheck', () => {