diff --git a/packages/core/test/acceptance/lifecycle_spec.ts b/packages/core/test/acceptance/lifecycle_spec.ts
index ba58110df6..611876f0fd 100644
--- a/packages/core/test/acceptance/lifecycle_spec.ts
+++ b/packages/core/test/acceptance/lifecycle_spec.ts
@@ -8,11 +8,12 @@
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('ngOnChanges', () => {
+describe('onChanges', () => {
it('should correctly support updating one Input among many', () => {
let log: string[] = [];
@@ -59,6 +60,930 @@ describe('ngOnChanges', () => {
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: `
test
`,
+ })
+ class Comp {
+ @Input()
+ val1 = 'a';
+
+ @Input('publicVal2')
+ val2 = 'b';
+
+ ngOnChanges(changes: SimpleChanges) { events.push({name: 'comp', changes}); }
+ }
+
+ @Component({template: ``})
+ 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: ``,
+ })
+ class Parent {
+ @Input()
+ val = '';
+
+ ngOnChanges(changes: SimpleChanges) { events.push({name: 'parent', changes}); }
+ }
+
+ @Component({
+ selector: 'child',
+ template: `test
`,
+ })
+ class Child {
+ @Input()
+ val = '';
+
+ ngOnChanges(changes: SimpleChanges) { events.push({name: 'child', changes}); }
+ }
+
+ @Component({template: ``})
+ 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: ``,
+ })
+ class Parent {
+ @Input()
+ val = '';
+
+ @Input()
+ name = '';
+
+ ngOnChanges(changes: SimpleChanges) { events.push({name: 'parent ' + this.name, changes}); }
+ }
+
+ @Component({
+ selector: 'child',
+ template: `test
`,
+ })
+ class Child {
+ @Input()
+ val = '';
+
+ @Input()
+ name = '';
+
+ ngOnChanges(changes: SimpleChanges) { events.push({name: 'child ' + this.name, changes}); }
+ }
+
+ @Component({
+ template: `
+
+
+ `
+ })
+ 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: `{{val}}
`,
+ })
+ class Comp {
+ @Input()
+ val = '';
+
+ ngOnChanges(changes: SimpleChanges) { events.push({name: 'comp', changes}); }
+ }
+
+ @Component({template: ``})
+ 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: `{{val}}
`,
+ })
+ class Projected {
+ @Input()
+ val = '';
+
+ ngOnChanges(changes: SimpleChanges) { events.push({name: 'projected', changes}); }
+ }
+
+ @Component({
+ selector: 'comp',
+ template: `
`,
+ })
+ class Comp {
+ @Input()
+ val = '';
+
+ ngOnChanges(changes: SimpleChanges) { events.push({name: 'comp', changes}); }
+ }
+
+ @Component({
+ template: ``,
+ })
+ 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: `{{val}}
`,
+ })
+ class Projected {
+ @Input()
+ val = '';
+
+ @Input()
+ name = '';
+
+ ngOnChanges(changes: SimpleChanges) {
+ events.push({name: 'projected ' + this.name, changes});
+ }
+ }
+
+ @Component({
+ selector: 'comp',
+ template: `
`,
+ })
+ class Comp {
+ @Input()
+ val = '';
+
+ @Input()
+ name = '';
+
+ ngOnChanges(changes: SimpleChanges) { events.push({name: 'comp ' + this.name, changes}); }
+ }
+
+ @Component({
+ template: `
+
+
+
+
+
+
+ `,
+ })
+ 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', () => {
+ const events: any[] = [];
+
+ @Directive({
+ selector: '[dir]',
+ })
+ class Dir {
+ @Input()
+ dir = '';
+
+ ngOnChanges(changes: SimpleChanges) { events.push({name: 'dir', changes}); }
+ }
+
+ @Component({
+ selector: 'comp',
+ template: `{{val}}
`,
+ })
+ class Comp {
+ @Input()
+ val = '';
+
+ ngOnChanges(changes: SimpleChanges) { events.push({name: 'comp', changes}); }
+ }
+
+ @Component({
+ template: ``,
+ })
+ 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 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: ``})
+ 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: `{{val}}
`,
+ })
+ class Comp {
+ @Input()
+ val = '';
+
+ @Input()
+ name = '';
+
+ ngOnChanges(changes: SimpleChanges) { events.push({name: 'comp ' + this.name, changes}); }
+ }
+
+ @Component({
+ template: `
+
+
+
+ `
+ })
+ 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: `{{val}}
`,
+ })
+ class Child {
+ @Input()
+ val = '';
+
+ @Input()
+ name = '';
+
+ ngOnChanges(changes: SimpleChanges) {
+ events.push({name: 'child of parent ' + this.name, changes});
+ }
+ }
+
+ @Component({
+ selector: 'parent',
+ template: ``,
+ })
+ class Parent {
+ @Input()
+ val = '';
+
+ @Input()
+ name = '';
+
+ ngOnChanges(changes: SimpleChanges) { events.push({name: 'parent ' + this.name, changes}); }
+ }
+
+ @Component({
+ template: `
+
+
+
+ `
+ })
+ 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: `{{value}}
`})
+ 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', () => {
diff --git a/packages/core/test/render3/lifecycle_spec.ts b/packages/core/test/render3/lifecycle_spec.ts
index 858dc5a269..0c3c35edca 100644
--- a/packages/core/test/render3/lifecycle_spec.ts
+++ b/packages/core/test/render3/lifecycle_spec.ts
@@ -116,774 +116,6 @@ describe('lifecycles', () => {
});
});
- describe('onChanges', () => {
- let events: ({type: string, name: string, [key: string]: any})[];
-
- beforeEach(() => { events = []; });
-
- /**
- *
- *
- *
- */
- const Comp = createOnChangesComponent('comp', (rf: RenderFlags, ctx: any) => {
- if (rf & RenderFlags.Create) {
- ΔprojectionDef();
- ΔelementStart(0, 'div');
- { Δprojection(1); }
- ΔelementEnd();
- }
- }, 2);
-
- /**
- *
- */
- const Parent = createOnChangesComponent('parent', (rf: RenderFlags, ctx: any) => {
- if (rf & RenderFlags.Create) {
- Δelement(0, 'comp');
- }
- if (rf & RenderFlags.Update) {
- ΔelementProperty(0, 'val1', Δbind(ctx.a));
- ΔelementProperty(0, 'publicVal2', Δbind(ctx.b));
- }
- }, 1, 2, [Comp]);
-
- const ProjectedComp = createOnChangesComponent('projected', (rf: RenderFlags, ctx: any) => {
- if (rf & RenderFlags.Create) {
- Δtext(0, 'content');
- }
- }, 1);
-
-
- function createOnChangesComponent(
- name: string, template: ComponentTemplate, consts: number = 0, vars: number = 0,
- directives: any[] = []) {
- return class Component {
- // @Input() val1: string;
- // @Input('publicVal2') val2: string;
- a: string = 'wasVal1BeforeMinification';
- b: string = 'wasVal2BeforeMinification';
- ngOnChanges(changes: SimpleChanges) {
- if (changes.a && this.a !== changes.a.currentValue) {
- throw Error(
- `SimpleChanges invalid expected this.a ${this.a} to equal currentValue ${changes.a.currentValue}`);
- }
- if (changes.b && this.b !== changes.b.currentValue) {
- throw Error(
- `SimpleChanges invalid expected this.b ${this.b} to equal currentValue ${changes.b.currentValue}`);
- }
- events.push({type: 'onChanges', name: 'comp - ' + name, changes});
- }
-
- static ngComponentDef = ΔdefineComponent({
- type: Component,
- selectors: [[name]],
- factory: () => new Component(),
- consts: consts,
- vars: vars,
- inputs: {a: 'val1', b: ['publicVal2', 'val2']}, template,
- directives: directives,
- features: [ΔNgOnChangesFeature()],
- });
- };
- }
-
- class Directive {
- // @Input() val1: string;
- // @Input('publicVal2') val2: string;
- a: string = 'wasVal1BeforeMinification';
- b: string = 'wasVal2BeforeMinification';
- ngOnChanges(changes: SimpleChanges) {
- events.push({type: 'onChanges', name: 'dir - dir', changes});
- }
-
- static ngDirectiveDef = ΔdefineDirective({
- type: Directive,
- selectors: [['', 'dir', '']],
- factory: () => new Directive(),
- inputs: {a: 'val1', b: ['publicVal2', 'val2']},
- features: [ΔNgOnChangesFeature()],
- });
- }
-
- const defs = [Comp, Parent, Directive, ProjectedComp];
-
- it('should call onChanges method after inputs are set in creation and update mode', () => {
- /** */
- const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- Δelement(0, 'comp');
- }
- if (rf & RenderFlags.Update) {
- ΔelementProperty(0, 'val1', Δbind(ctx.val1));
- ΔelementProperty(0, 'publicVal2', Δbind(ctx.val2));
- }
- }, 1, 2, defs);
-
- // First changes happen here.
- const fixture = new ComponentFixture(App);
-
- events = [];
- fixture.component.val1 = '1';
- fixture.component.val2 = 'a';
- fixture.update();
- expect(events).toEqual([{
- type: 'onChanges',
- name: 'comp - comp',
- changes: {
- 'val1': new SimpleChange(
- undefined, '1', false), // we cleared `events` above, this is the second change
- 'val2': new SimpleChange(undefined, 'a', false),
- }
- }]);
-
- events = [];
- fixture.component.val1 = '2';
- fixture.component.val2 = 'b';
- fixture.update();
- expect(events).toEqual([{
- type: 'onChanges',
- name: 'comp - comp',
- changes: {
- 'val1': new SimpleChange('1', '2', false),
- 'val2': new SimpleChange('a', 'b', false),
- }
- }]);
- });
-
- it('should call parent onChanges before child onChanges', () => {
- /**
- *
- * parent temp:
- */
- const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- Δelement(0, 'parent');
- }
- if (rf & RenderFlags.Update) {
- ΔelementProperty(0, 'val1', Δbind(ctx.val1));
- ΔelementProperty(0, 'publicVal2', Δbind(ctx.val2));
- }
- }, 1, 2, defs);
-
- const fixture = new ComponentFixture(App);
-
- // We're clearing events after the first change here
- events = [];
- fixture.component.val1 = '1';
- fixture.component.val2 = 'a';
- fixture.update();
-
- expect(events).toEqual([
- {
- type: 'onChanges',
- name: 'comp - parent',
- changes: {
- 'val1': new SimpleChange(undefined, '1', false),
- 'val2': new SimpleChange(undefined, 'a', false),
- }
- },
- {
- type: 'onChanges',
- name: 'comp - comp',
- changes: {
- 'val1': new SimpleChange(undefined, '1', false),
- 'val2': new SimpleChange(undefined, 'a', false),
- }
- },
- ]);
- });
-
- it('should call all parent onChanges across view before calling children onChanges', () => {
- /**
- *
- *
- */
-
- 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, 'val1', Δbind(1));
- ΔelementProperty(0, 'publicVal2', Δbind(1));
- Δselect(1);
- ΔelementProperty(1, 'val1', Δbind(2));
- ΔelementProperty(1, 'publicVal2', Δbind(2));
- }
- }, 2, 4, defs);
-
- const fixture = new ComponentFixture(App);
- expect(events).toEqual([
- {
- type: 'onChanges',
- name: 'comp - parent',
- changes: {
- 'val1': new SimpleChange(undefined, 1, true),
- 'val2': new SimpleChange(undefined, 1, true),
- }
- },
- {
- type: 'onChanges',
- name: 'comp - parent',
- changes: {
- 'val1': new SimpleChange(undefined, 2, true),
- 'val2': new SimpleChange(undefined, 2, true),
- }
- },
- {
- type: 'onChanges',
- name: 'comp - comp',
- changes: {
- 'val1': new SimpleChange(undefined, 1, true),
- 'val2': new SimpleChange(undefined, 1, true),
- }
- },
- {
- type: 'onChanges',
- name: 'comp - comp',
- changes: {
- 'val1': new SimpleChange(undefined, 2, true),
- 'val2': new SimpleChange(undefined, 2, true),
- }
- },
- ]);
- });
-
-
- it('should call onChanges every time a new view is created (if block)', () => {
- /**
- * % if (condition) {
- *
- * % }
- */
-
- const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- Δcontainer(0);
- }
- if (rf & RenderFlags.Update) {
- ΔcontainerRefreshStart(0);
- {
- if (ctx.condition) {
- let rf1 = ΔembeddedViewStart(0, 1, 2);
- if (rf1 & RenderFlags.Create) {
- Δelement(0, 'comp');
- }
- if (rf1 & RenderFlags.Update) {
- ΔelementProperty(0, 'val1', Δbind(1));
- ΔelementProperty(0, 'publicVal2', Δbind(1));
- }
- ΔembeddedViewEnd();
- }
- }
- ΔcontainerRefreshEnd();
- }
- }, 1, 0, defs);
-
- const fixture = new ComponentFixture(App);
-
- // Show the `comp` component, causing it to initialize. (first change is true)
- fixture.component.condition = true;
- fixture.update();
- expect(events).toEqual([{
- type: 'onChanges',
- name: 'comp - comp',
- changes: {
- 'val1': new SimpleChange(undefined, 1, true),
- 'val2': new SimpleChange(undefined, 1, true),
- }
- }]);
-
- // Hide the `comp` component, no onChanges should fire
- fixture.component.condition = false;
- fixture.update();
- expect(events).toEqual([{
- type: 'onChanges',
- name: 'comp - comp',
- changes: {
- 'val1': new SimpleChange(undefined, 1, true),
- 'val2': new SimpleChange(undefined, 1, true),
- }
- }]);
-
- // Show the `comp` component, it initializes again. (first change is true)
- fixture.component.condition = true;
- fixture.update();
- expect(events).toEqual([
- {
- type: 'onChanges',
- name: 'comp - comp',
- changes: {
- 'val1': new SimpleChange(undefined, 1, true),
- 'val2': new SimpleChange(undefined, 1, true),
- }
- },
- {
- type: 'onChanges',
- name: 'comp - comp',
- changes: {
- 'val1': new SimpleChange(undefined, 1, true),
- 'val2': new SimpleChange(undefined, 1, true),
- }
- }
- ]);
- });
-
- it('should call onChanges 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();
- }
- if (rf & RenderFlags.Update) {
- ΔelementProperty(0, 'val1', Δbind(1));
- ΔelementProperty(0, 'publicVal2', Δbind(1));
- Δselect(1);
- ΔelementProperty(1, 'val1', Δbind(2));
- ΔelementProperty(1, 'publicVal2', Δbind(2));
- }
- }, 2, 4, defs);
-
- const fixture = new ComponentFixture(App);
- expect(events).toEqual([
- {
- type: 'onChanges',
- name: 'comp - comp',
- changes: {
- 'val1': new SimpleChange(undefined, 1, true),
- 'val2': new SimpleChange(undefined, 1, true),
- }
- },
- {
- type: 'onChanges',
- name: 'comp - projected',
- changes: {
- 'val1': new SimpleChange(undefined, 2, true),
- 'val2': new SimpleChange(undefined, 2, true),
- }
- },
- ]);
- });
-
- it('should call onChanges 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, 'val1', Δbind(1));
- ΔelementProperty(0, 'publicVal2', Δbind(1));
- Δselect(1);
- ΔelementProperty(1, 'val1', Δbind(2));
- ΔelementProperty(1, 'publicVal2', Δbind(2));
- Δselect(2);
- ΔelementProperty(2, 'val1', Δbind(3));
- ΔelementProperty(2, 'publicVal2', Δbind(3));
- Δselect(3);
- ΔelementProperty(3, 'val1', Δbind(4));
- ΔelementProperty(3, 'publicVal2', Δbind(4));
- }
- }, 4, 8, defs);
-
- const fixture = new ComponentFixture(App);
- expect(events).toEqual([
- {
- type: 'onChanges',
- name: 'comp - comp',
- changes: {
- 'val1': new SimpleChange(undefined, 1, true),
- 'val2': new SimpleChange(undefined, 1, true),
- }
- },
- {
- type: 'onChanges',
- name: 'comp - projected',
- changes: {
- 'val1': new SimpleChange(undefined, 2, true),
- 'val2': new SimpleChange(undefined, 2, true),
- }
- },
- {
- type: 'onChanges',
- name: 'comp - comp',
- changes: {
- 'val1': new SimpleChange(undefined, 3, true),
- 'val2': new SimpleChange(undefined, 3, true),
- }
- },
- {
- type: 'onChanges',
- name: 'comp - projected',
- changes: {
- 'val1': new SimpleChange(undefined, 4, true),
- 'val2': new SimpleChange(undefined, 4, true),
- }
- },
- ]);
- });
-
- 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', '']);
- }
- if (rf & RenderFlags.Update) {
- ΔelementProperty(0, 'val1', Δbind(1));
- ΔelementProperty(0, 'publicVal2', Δbind(1));
- }
- }, 1, 2, defs);
-
- const fixture = new ComponentFixture(App);
- expect(events).toEqual([
- {
- type: 'onChanges',
- name: 'comp - comp',
- changes: {
- 'val1': new SimpleChange(undefined, 1, true),
- 'val2': new SimpleChange(undefined, 1, true),
- }
- },
- {
- type: 'onChanges',
- name: 'dir - dir',
- changes: {
- 'val1': new SimpleChange(undefined, 1, true),
- 'val2': new SimpleChange(undefined, 1, true),
- }
- },
- ]);
-
- // Update causes no changes to be fired, since the bindings didn't change.
- events = [];
- fixture.update();
- expect(events).toEqual([]);
-
- });
-
- 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', '']);
- }
- if (rf & RenderFlags.Update) {
- ΔelementProperty(0, 'val1', Δbind(1));
- ΔelementProperty(0, 'publicVal2', Δbind(1));
- }
- }, 1, 2, defs);
-
- const fixture = new ComponentFixture(App);
- expect(events).toEqual([{
- type: 'onChanges',
- name: 'dir - dir',
- changes: {
- 'val1': new SimpleChange(undefined, 1, true),
- 'val2': new SimpleChange(undefined, 1, true),
- }
- }]);
-
- events = [];
- fixture.update();
- expect(events).toEqual([]);
- });
-
- it('should call onChanges 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, 'val1', Δbind(1));
- ΔelementProperty(0, 'publicVal2', Δbind(1));
- Δselect(2);
- ΔelementProperty(2, 'val1', Δbind(5));
- ΔelementProperty(2, 'publicVal2', Δbind(5));
- ΔcontainerRefreshStart(1);
- {
- for (let j = 2; j < 5; j++) {
- let rf1 = ΔembeddedViewStart(0, 1, 2);
- if (rf1 & RenderFlags.Create) {
- Δelement(0, 'comp');
- }
- if (rf1 & RenderFlags.Update) {
- ΔelementProperty(0, 'val1', Δbind(j));
- ΔelementProperty(0, 'publicVal2', Δbind(j));
- }
- ΔembeddedViewEnd();
- }
- }
- ΔcontainerRefreshEnd();
- }
- }, 3, 4, defs);
-
- const fixture = new ComponentFixture(App);
-
- // onChanges 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([
- {
- type: 'onChanges',
- name: 'comp - comp',
- changes: {
- 'val1': new SimpleChange(undefined, 1, true),
- 'val2': new SimpleChange(undefined, 1, true),
- }
- },
- {
- type: 'onChanges',
- name: 'comp - comp',
- changes: {
- 'val1': new SimpleChange(undefined, 5, true),
- 'val2': new SimpleChange(undefined, 5, true),
- }
- },
- {
- type: 'onChanges',
- name: 'comp - comp',
- changes: {
- 'val1': new SimpleChange(undefined, 2, true),
- 'val2': new SimpleChange(undefined, 2, true),
- }
- },
- {
- type: 'onChanges',
- name: 'comp - comp',
- changes: {
- 'val1': new SimpleChange(undefined, 3, true),
- 'val2': new SimpleChange(undefined, 3, true),
- }
- },
- {
- type: 'onChanges',
- name: 'comp - comp',
- changes: {
- 'val1': new SimpleChange(undefined, 4, true),
- 'val2': new SimpleChange(undefined, 4, true),
- }
- },
- ]);
- });
-
- it('should call onChanges 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, 'val1', Δbind(1));
- ΔelementProperty(0, 'publicVal2', Δbind(1));
- Δselect(2);
- ΔelementProperty(2, 'val1', Δbind(5));
- ΔelementProperty(2, 'publicVal2', Δbind(5));
- ΔcontainerRefreshStart(1);
- {
- for (let j = 2; j < 5; j++) {
- let rf1 = ΔembeddedViewStart(0, 1, 2);
- if (rf1 & RenderFlags.Create) {
- Δelement(0, 'parent');
- }
- if (rf1 & RenderFlags.Update) {
- ΔelementProperty(0, 'val1', Δbind(j));
- ΔelementProperty(0, 'publicVal2', Δbind(j));
- }
- ΔembeddedViewEnd();
- }
- }
- ΔcontainerRefreshEnd();
- }
- }, 3, 4, defs);
-
- const fixture = new ComponentFixture(App);
-
- // onChanges 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([
- {
- type: 'onChanges',
- name: 'comp - parent',
- changes: {
- 'val1': new SimpleChange(undefined, 1, true),
- 'val2': new SimpleChange(undefined, 1, true),
- }
- },
- {
- type: 'onChanges',
- name: 'comp - parent',
- changes: {
- 'val1': new SimpleChange(undefined, 5, true),
- 'val2': new SimpleChange(undefined, 5, true),
- }
- },
- {
- type: 'onChanges',
- name: 'comp - parent',
- changes: {
- 'val1': new SimpleChange(undefined, 2, true),
- 'val2': new SimpleChange(undefined, 2, true),
- }
- },
- {
- type: 'onChanges',
- name: 'comp - comp',
- changes: {
- 'val1': new SimpleChange(undefined, 2, true),
- 'val2': new SimpleChange(undefined, 2, true),
- }
- },
- {
- type: 'onChanges',
- name: 'comp - parent',
- changes: {
- 'val1': new SimpleChange(undefined, 3, true),
- 'val2': new SimpleChange(undefined, 3, true),
- }
- },
- {
- type: 'onChanges',
- name: 'comp - comp',
- changes: {
- 'val1': new SimpleChange(undefined, 3, true),
- 'val2': new SimpleChange(undefined, 3, true),
- }
- },
- {
- type: 'onChanges',
- name: 'comp - parent',
- changes: {
- 'val1': new SimpleChange(undefined, 4, true),
- 'val2': new SimpleChange(undefined, 4, true),
- }
- },
- {
- type: 'onChanges',
- name: 'comp - comp',
- changes: {
- 'val1': new SimpleChange(undefined, 4, true),
- 'val2': new SimpleChange(undefined, 4, true),
- }
- },
- {
- type: 'onChanges',
- name: 'comp - comp',
- changes: {
- 'val1': new SimpleChange(undefined, 1, true),
- 'val2': new SimpleChange(undefined, 1, true),
- }
- },
- {
- type: 'onChanges',
- name: 'comp - comp',
- changes: {
- 'val1': new SimpleChange(undefined, 5, true),
- 'val2': new SimpleChange(undefined, 5, true),
- }
- },
- ]);
- });
-
- it('should not call onChanges if props are set directly', () => {
- let events: SimpleChanges[] = [];
- let compInstance: MyComp;
- class MyComp {
- value = 0;
-
- ngOnChanges(changes: SimpleChanges) { events.push(changes); }
-
- static ngComponentDef = ΔdefineComponent({
- type: MyComp,
- factory: () => {
- // Capture the instance so we can test setting the property directly
- compInstance = new MyComp();
- return compInstance;
- },
- template: (rf: RenderFlags, ctx: any) => {
- if (rf & RenderFlags.Create) {
- Δelement(0, 'div');
- }
- if (rf & RenderFlags.Update) {
- ΔelementProperty(0, 'id', Δbind(ctx.a));
- }
- },
- selectors: [['my-comp']],
- inputs: {
- value: 'value',
- },
- consts: 1,
- vars: 1,
- });
- }
-
- /**
- *
- */
-
- const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- Δelement(0, 'my-comp');
- }
- if (rf & RenderFlags.Update) {
- ΔelementProperty(0, 'value', Δbind(1));
- }
- }, 1, 1, [MyComp]);
-
- const fixture = new ComponentFixture(App);
- events = [];
-
- // Try setting the property directly
- compInstance !.value = 2;
-
- fixture.update();
- expect(events).toEqual([]);
- });
-
- });
-
describe('hook order', () => {
let events: string[];