refactor(ivy): Add newer, smaller NgOnChangesFeature (#28187)
PR Close #28187
This commit is contained in:

committed by
Alex Rickabaugh

parent
5552661fd7
commit
a95e81978b
@ -315,239 +315,235 @@ withEachNg1Version(() => {
|
||||
});
|
||||
}));
|
||||
|
||||
fixmeIvy('FW-715: ngOnChanges being called a second time unexpectedly')
|
||||
.it('should bind properties, events', async(() => {
|
||||
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
|
||||
const ng1Module = angular.module('ng1', []).value(
|
||||
$EXCEPTION_HANDLER, (err: any) => { throw err; });
|
||||
it('should bind properties, events', async(() => {
|
||||
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
|
||||
const ng1Module =
|
||||
angular.module('ng1', []).value($EXCEPTION_HANDLER, (err: any) => { throw err; });
|
||||
|
||||
ng1Module.run(($rootScope: any) => {
|
||||
$rootScope.name = 'world';
|
||||
$rootScope.dataA = 'A';
|
||||
$rootScope.dataB = 'B';
|
||||
$rootScope.modelA = 'initModelA';
|
||||
$rootScope.modelB = 'initModelB';
|
||||
$rootScope.eventA = '?';
|
||||
$rootScope.eventB = '?';
|
||||
});
|
||||
@Component({
|
||||
selector: 'ng2',
|
||||
inputs: ['literal', 'interpolate', 'oneWayA', 'oneWayB', 'twoWayA', 'twoWayB'],
|
||||
outputs: [
|
||||
'eventA', 'eventB', 'twoWayAEmitter: twoWayAChange',
|
||||
'twoWayBEmitter: twoWayBChange'
|
||||
],
|
||||
template: 'ignore: {{ignore}}; ' +
|
||||
'literal: {{literal}}; interpolate: {{interpolate}}; ' +
|
||||
'oneWayA: {{oneWayA}}; oneWayB: {{oneWayB}}; ' +
|
||||
'twoWayA: {{twoWayA}}; twoWayB: {{twoWayB}}; ({{ngOnChangesCount}})'
|
||||
})
|
||||
class Ng2 {
|
||||
ngOnChangesCount = 0;
|
||||
ignore = '-';
|
||||
literal = '?';
|
||||
interpolate = '?';
|
||||
oneWayA = '?';
|
||||
oneWayB = '?';
|
||||
twoWayA = '?';
|
||||
twoWayB = '?';
|
||||
eventA = new EventEmitter();
|
||||
eventB = new EventEmitter();
|
||||
twoWayAEmitter = new EventEmitter();
|
||||
twoWayBEmitter = new EventEmitter();
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
const assert = (prop: string, value: any) => {
|
||||
if ((this as any)[prop] != value) {
|
||||
throw new Error(
|
||||
`Expected: '${prop}' to be '${value}' but was '${(this as any)[prop]}'`);
|
||||
}
|
||||
};
|
||||
ng1Module.run(($rootScope: any) => {
|
||||
$rootScope.name = 'world';
|
||||
$rootScope.dataA = 'A';
|
||||
$rootScope.dataB = 'B';
|
||||
$rootScope.modelA = 'initModelA';
|
||||
$rootScope.modelB = 'initModelB';
|
||||
$rootScope.eventA = '?';
|
||||
$rootScope.eventB = '?';
|
||||
});
|
||||
@Component({
|
||||
selector: 'ng2',
|
||||
inputs: ['literal', 'interpolate', 'oneWayA', 'oneWayB', 'twoWayA', 'twoWayB'],
|
||||
outputs: [
|
||||
'eventA', 'eventB', 'twoWayAEmitter: twoWayAChange', 'twoWayBEmitter: twoWayBChange'
|
||||
],
|
||||
template: 'ignore: {{ignore}}; ' +
|
||||
'literal: {{literal}}; interpolate: {{interpolate}}; ' +
|
||||
'oneWayA: {{oneWayA}}; oneWayB: {{oneWayB}}; ' +
|
||||
'twoWayA: {{twoWayA}}; twoWayB: {{twoWayB}}; ({{ngOnChangesCount}})'
|
||||
})
|
||||
class Ng2 {
|
||||
ngOnChangesCount = 0;
|
||||
ignore = '-';
|
||||
literal = '?';
|
||||
interpolate = '?';
|
||||
oneWayA = '?';
|
||||
oneWayB = '?';
|
||||
twoWayA = '?';
|
||||
twoWayB = '?';
|
||||
eventA = new EventEmitter();
|
||||
eventB = new EventEmitter();
|
||||
twoWayAEmitter = new EventEmitter();
|
||||
twoWayBEmitter = new EventEmitter();
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
const assert = (prop: string, value: any) => {
|
||||
if ((this as any)[prop] != value) {
|
||||
throw new Error(
|
||||
`Expected: '${prop}' to be '${value}' but was '${(this as any)[prop]}'`);
|
||||
}
|
||||
};
|
||||
|
||||
const assertChange = (prop: string, value: any) => {
|
||||
assert(prop, value);
|
||||
if (!changes[prop]) {
|
||||
throw new Error(`Changes record for '${prop}' not found.`);
|
||||
}
|
||||
const actValue = changes[prop].currentValue;
|
||||
if (actValue != value) {
|
||||
throw new Error(
|
||||
`Expected changes record for'${prop}' to be '${value}' but was '${actValue}'`);
|
||||
}
|
||||
};
|
||||
const assertChange = (prop: string, value: any) => {
|
||||
assert(prop, value);
|
||||
if (!changes[prop]) {
|
||||
throw new Error(`Changes record for '${prop}' not found.`);
|
||||
}
|
||||
const actValue = changes[prop].currentValue;
|
||||
if (actValue != value) {
|
||||
throw new Error(
|
||||
`Expected changes record for'${prop}' to be '${value}' but was '${actValue}'`);
|
||||
}
|
||||
};
|
||||
|
||||
switch (this.ngOnChangesCount++) {
|
||||
case 0:
|
||||
assert('ignore', '-');
|
||||
assertChange('literal', 'Text');
|
||||
assertChange('interpolate', 'Hello world');
|
||||
assertChange('oneWayA', 'A');
|
||||
assertChange('oneWayB', 'B');
|
||||
assertChange('twoWayA', 'initModelA');
|
||||
assertChange('twoWayB', 'initModelB');
|
||||
switch (this.ngOnChangesCount++) {
|
||||
case 0:
|
||||
assert('ignore', '-');
|
||||
assertChange('literal', 'Text');
|
||||
assertChange('interpolate', 'Hello world');
|
||||
assertChange('oneWayA', 'A');
|
||||
assertChange('oneWayB', 'B');
|
||||
assertChange('twoWayA', 'initModelA');
|
||||
assertChange('twoWayB', 'initModelB');
|
||||
|
||||
this.twoWayAEmitter.emit('newA');
|
||||
this.twoWayBEmitter.emit('newB');
|
||||
this.eventA.emit('aFired');
|
||||
this.eventB.emit('bFired');
|
||||
break;
|
||||
case 1:
|
||||
assertChange('twoWayA', 'newA');
|
||||
assertChange('twoWayB', 'newB');
|
||||
break;
|
||||
case 2:
|
||||
assertChange('interpolate', 'Hello everyone');
|
||||
break;
|
||||
default:
|
||||
throw new Error('Called too many times! ' + JSON.stringify(changes));
|
||||
}
|
||||
}
|
||||
}
|
||||
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
|
||||
this.twoWayAEmitter.emit('newA');
|
||||
this.twoWayBEmitter.emit('newB');
|
||||
this.eventA.emit('aFired');
|
||||
this.eventB.emit('bFired');
|
||||
break;
|
||||
case 1:
|
||||
assertChange('twoWayA', 'newA');
|
||||
assertChange('twoWayB', 'newB');
|
||||
break;
|
||||
case 2:
|
||||
assertChange('interpolate', 'Hello everyone');
|
||||
break;
|
||||
default:
|
||||
throw new Error('Called too many times! ' + JSON.stringify(changes));
|
||||
}
|
||||
}
|
||||
}
|
||||
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
|
||||
|
||||
@NgModule({
|
||||
declarations: [Ng2],
|
||||
imports: [BrowserModule],
|
||||
})
|
||||
class Ng2Module {
|
||||
}
|
||||
@NgModule({
|
||||
declarations: [Ng2],
|
||||
imports: [BrowserModule],
|
||||
})
|
||||
class Ng2Module {
|
||||
}
|
||||
|
||||
const element = html(`<div>
|
||||
const element = html(`<div>
|
||||
<ng2 literal="Text" interpolate="Hello {{name}}"
|
||||
bind-one-way-a="dataA" [one-way-b]="dataB"
|
||||
bindon-two-way-a="modelA" [(two-way-b)]="modelB"
|
||||
on-event-a='eventA=$event' (event-b)="eventB=$event"></ng2>
|
||||
| modelA: {{modelA}}; modelB: {{modelB}}; eventA: {{eventA}}; eventB: {{eventB}};
|
||||
</div>`);
|
||||
adapter.bootstrap(element, ['ng1']).ready((ref) => {
|
||||
expect(multiTrim(document.body.textContent !))
|
||||
.toEqual(
|
||||
'ignore: -; ' +
|
||||
'literal: Text; interpolate: Hello world; ' +
|
||||
'oneWayA: A; oneWayB: B; twoWayA: newA; twoWayB: newB; (2) | ' +
|
||||
'modelA: newA; modelB: newB; eventA: aFired; eventB: bFired;');
|
||||
adapter.bootstrap(element, ['ng1']).ready((ref) => {
|
||||
expect(multiTrim(document.body.textContent !))
|
||||
.toEqual(
|
||||
'ignore: -; ' +
|
||||
'literal: Text; interpolate: Hello world; ' +
|
||||
'oneWayA: A; oneWayB: B; twoWayA: newA; twoWayB: newB; (2) | ' +
|
||||
'modelA: newA; modelB: newB; eventA: aFired; eventB: bFired;');
|
||||
|
||||
ref.ng1RootScope.$apply('name = "everyone"');
|
||||
expect(multiTrim(document.body.textContent !))
|
||||
.toEqual(
|
||||
'ignore: -; ' +
|
||||
'literal: Text; interpolate: Hello everyone; ' +
|
||||
'oneWayA: A; oneWayB: B; twoWayA: newA; twoWayB: newB; (3) | ' +
|
||||
'modelA: newA; modelB: newB; eventA: aFired; eventB: bFired;');
|
||||
ref.ng1RootScope.$apply('name = "everyone"');
|
||||
expect(multiTrim(document.body.textContent !))
|
||||
.toEqual(
|
||||
'ignore: -; ' +
|
||||
'literal: Text; interpolate: Hello everyone; ' +
|
||||
'oneWayA: A; oneWayB: B; twoWayA: newA; twoWayB: newB; (3) | ' +
|
||||
'modelA: newA; modelB: newB; eventA: aFired; eventB: bFired;');
|
||||
|
||||
ref.dispose();
|
||||
});
|
||||
ref.dispose();
|
||||
});
|
||||
|
||||
}));
|
||||
}));
|
||||
|
||||
fixmeIvy('FW-715: ngOnChanges being called a second time unexpectedly')
|
||||
.it('should support two-way binding and event listener', async(() => {
|
||||
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
|
||||
const listenerSpy = jasmine.createSpy('$rootScope.listener');
|
||||
const ng1Module = angular.module('ng1', []).run(($rootScope: angular.IScope) => {
|
||||
$rootScope['value'] = 'world';
|
||||
$rootScope['listener'] = listenerSpy;
|
||||
});
|
||||
it('should support two-way binding and event listener', async(() => {
|
||||
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
|
||||
const listenerSpy = jasmine.createSpy('$rootScope.listener');
|
||||
const ng1Module = angular.module('ng1', []).run(($rootScope: angular.IScope) => {
|
||||
$rootScope['value'] = 'world';
|
||||
$rootScope['listener'] = listenerSpy;
|
||||
});
|
||||
|
||||
@Component({selector: 'ng2', template: `model: {{model}};`})
|
||||
class Ng2Component implements OnChanges {
|
||||
ngOnChangesCount = 0;
|
||||
@Input() model = '?';
|
||||
@Output() modelChange = new EventEmitter();
|
||||
@Component({selector: 'ng2', template: `model: {{model}};`})
|
||||
class Ng2Component implements OnChanges {
|
||||
ngOnChangesCount = 0;
|
||||
@Input() model = '?';
|
||||
@Output() modelChange = new EventEmitter();
|
||||
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
switch (this.ngOnChangesCount++) {
|
||||
case 0:
|
||||
expect(changes.model.currentValue).toBe('world');
|
||||
this.modelChange.emit('newC');
|
||||
break;
|
||||
case 1:
|
||||
expect(changes.model.currentValue).toBe('newC');
|
||||
break;
|
||||
default:
|
||||
throw new Error('Called too many times! ' + JSON.stringify(changes));
|
||||
}
|
||||
}
|
||||
}
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
switch (this.ngOnChangesCount++) {
|
||||
case 0:
|
||||
expect(changes.model.currentValue).toBe('world');
|
||||
this.modelChange.emit('newC');
|
||||
break;
|
||||
case 1:
|
||||
expect(changes.model.currentValue).toBe('newC');
|
||||
break;
|
||||
default:
|
||||
throw new Error('Called too many times! ' + JSON.stringify(changes));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2Component));
|
||||
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2Component));
|
||||
|
||||
@NgModule({declarations: [Ng2Component], imports: [BrowserModule]})
|
||||
class Ng2Module {
|
||||
ngDoBootstrap() {}
|
||||
}
|
||||
@NgModule({declarations: [Ng2Component], imports: [BrowserModule]})
|
||||
class Ng2Module {
|
||||
ngDoBootstrap() {}
|
||||
}
|
||||
|
||||
const element = html(`
|
||||
const element = html(`
|
||||
<div>
|
||||
<ng2 [(model)]="value" (model-change)="listener($event)"></ng2>
|
||||
| value: {{value}}
|
||||
</div>
|
||||
`);
|
||||
adapter.bootstrap(element, ['ng1']).ready((ref) => {
|
||||
expect(multiTrim(element.textContent)).toEqual('model: newC; | value: newC');
|
||||
expect(listenerSpy).toHaveBeenCalledWith('newC');
|
||||
ref.dispose();
|
||||
});
|
||||
}));
|
||||
adapter.bootstrap(element, ['ng1']).ready((ref) => {
|
||||
expect(multiTrim(element.textContent)).toEqual('model: newC; | value: newC');
|
||||
expect(listenerSpy).toHaveBeenCalledWith('newC');
|
||||
ref.dispose();
|
||||
});
|
||||
}));
|
||||
|
||||
fixmeIvy('FW-715: ngOnChanges being called a second time unexpectedly')
|
||||
.it('should initialize inputs in time for `ngOnChanges`', async(() => {
|
||||
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
|
||||
it('should initialize inputs in time for `ngOnChanges`', async(() => {
|
||||
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
|
||||
|
||||
@Component({
|
||||
selector: 'ng2',
|
||||
template: `
|
||||
@Component({
|
||||
selector: 'ng2',
|
||||
template: `
|
||||
ngOnChangesCount: {{ ngOnChangesCount }} |
|
||||
firstChangesCount: {{ firstChangesCount }} |
|
||||
initialValue: {{ initialValue }}`
|
||||
})
|
||||
class Ng2Component implements OnChanges {
|
||||
ngOnChangesCount = 0;
|
||||
firstChangesCount = 0;
|
||||
// TODO(issue/24571): remove '!'.
|
||||
initialValue !: string;
|
||||
// TODO(issue/24571): remove '!'.
|
||||
@Input() foo !: string;
|
||||
})
|
||||
class Ng2Component implements OnChanges {
|
||||
ngOnChangesCount = 0;
|
||||
firstChangesCount = 0;
|
||||
// TODO(issue/24571): remove '!'.
|
||||
initialValue !: string;
|
||||
// TODO(issue/24571): remove '!'.
|
||||
@Input() foo !: string;
|
||||
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
this.ngOnChangesCount++;
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
this.ngOnChangesCount++;
|
||||
|
||||
if (this.ngOnChangesCount === 1) {
|
||||
this.initialValue = this.foo;
|
||||
}
|
||||
if (this.ngOnChangesCount === 1) {
|
||||
this.initialValue = this.foo;
|
||||
}
|
||||
|
||||
if (changes['foo'] && changes['foo'].isFirstChange()) {
|
||||
this.firstChangesCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (changes['foo'] && changes['foo'].isFirstChange()) {
|
||||
this.firstChangesCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@NgModule({imports: [BrowserModule], declarations: [Ng2Component]})
|
||||
class Ng2Module {
|
||||
}
|
||||
@NgModule({imports: [BrowserModule], declarations: [Ng2Component]})
|
||||
class Ng2Module {
|
||||
}
|
||||
|
||||
const ng1Module = angular.module('ng1', []).directive(
|
||||
'ng2', adapter.downgradeNg2Component(Ng2Component));
|
||||
const ng1Module = angular.module('ng1', []).directive(
|
||||
'ng2', adapter.downgradeNg2Component(Ng2Component));
|
||||
|
||||
const element = html(`
|
||||
const element = html(`
|
||||
<ng2 [foo]="'foo'"></ng2>
|
||||
<ng2 foo="bar"></ng2>
|
||||
<ng2 [foo]="'baz'" ng-if="true"></ng2>
|
||||
<ng2 foo="qux" ng-if="true"></ng2>
|
||||
`);
|
||||
|
||||
adapter.bootstrap(element, ['ng1']).ready(ref => {
|
||||
const nodes = element.querySelectorAll('ng2');
|
||||
const expectedTextWith = (value: string) =>
|
||||
`ngOnChangesCount: 1 | firstChangesCount: 1 | initialValue: ${value}`;
|
||||
adapter.bootstrap(element, ['ng1']).ready(ref => {
|
||||
const nodes = element.querySelectorAll('ng2');
|
||||
const expectedTextWith = (value: string) =>
|
||||
`ngOnChangesCount: 1 | firstChangesCount: 1 | initialValue: ${value}`;
|
||||
|
||||
expect(multiTrim(nodes[0].textContent)).toBe(expectedTextWith('foo'));
|
||||
expect(multiTrim(nodes[1].textContent)).toBe(expectedTextWith('bar'));
|
||||
expect(multiTrim(nodes[2].textContent)).toBe(expectedTextWith('baz'));
|
||||
expect(multiTrim(nodes[3].textContent)).toBe(expectedTextWith('qux'));
|
||||
expect(multiTrim(nodes[0].textContent)).toBe(expectedTextWith('foo'));
|
||||
expect(multiTrim(nodes[1].textContent)).toBe(expectedTextWith('bar'));
|
||||
expect(multiTrim(nodes[2].textContent)).toBe(expectedTextWith('baz'));
|
||||
expect(multiTrim(nodes[3].textContent)).toBe(expectedTextWith('qux'));
|
||||
|
||||
ref.dispose();
|
||||
});
|
||||
}));
|
||||
ref.dispose();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should bind to ng-model', async(() => {
|
||||
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
|
||||
@ -1872,7 +1868,6 @@ withEachNg1Version(() => {
|
||||
});
|
||||
}));
|
||||
|
||||
fixmeIvy('FW-956: refactor onChanges').
|
||||
it('should call `$onChanges()` on binding destination', fakeAsync(() => {
|
||||
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
|
||||
const $onChangesControllerSpyA = jasmine.createSpy('$onChangesControllerA');
|
||||
|
@ -22,106 +22,104 @@ withEachNg1Version(() => {
|
||||
beforeEach(() => destroyPlatform());
|
||||
afterEach(() => destroyPlatform());
|
||||
|
||||
fixmeIvy('FW-715: ngOnChanges being called a second time unexpectedly')
|
||||
.it('should bind properties, events', async(() => {
|
||||
const ng1Module = angular.module('ng1', []).run(($rootScope: angular.IScope) => {
|
||||
$rootScope['name'] = 'world';
|
||||
$rootScope['dataA'] = 'A';
|
||||
$rootScope['dataB'] = 'B';
|
||||
$rootScope['modelA'] = 'initModelA';
|
||||
$rootScope['modelB'] = 'initModelB';
|
||||
$rootScope['eventA'] = '?';
|
||||
$rootScope['eventB'] = '?';
|
||||
});
|
||||
it('should bind properties, events', async(() => {
|
||||
const ng1Module = angular.module('ng1', []).run(($rootScope: angular.IScope) => {
|
||||
$rootScope['name'] = 'world';
|
||||
$rootScope['dataA'] = 'A';
|
||||
$rootScope['dataB'] = 'B';
|
||||
$rootScope['modelA'] = 'initModelA';
|
||||
$rootScope['modelB'] = 'initModelB';
|
||||
$rootScope['eventA'] = '?';
|
||||
$rootScope['eventB'] = '?';
|
||||
});
|
||||
|
||||
@Component({
|
||||
selector: 'ng2',
|
||||
inputs: ['literal', 'interpolate', 'oneWayA', 'oneWayB', 'twoWayA', 'twoWayB'],
|
||||
outputs: [
|
||||
'eventA', 'eventB', 'twoWayAEmitter: twoWayAChange',
|
||||
'twoWayBEmitter: twoWayBChange'
|
||||
],
|
||||
template: 'ignore: {{ignore}}; ' +
|
||||
'literal: {{literal}}; interpolate: {{interpolate}}; ' +
|
||||
'oneWayA: {{oneWayA}}; oneWayB: {{oneWayB}}; ' +
|
||||
'twoWayA: {{twoWayA}}; twoWayB: {{twoWayB}}; ({{ngOnChangesCount}})'
|
||||
})
|
||||
class Ng2Component implements OnChanges {
|
||||
ngOnChangesCount = 0;
|
||||
ignore = '-';
|
||||
literal = '?';
|
||||
interpolate = '?';
|
||||
oneWayA = '?';
|
||||
oneWayB = '?';
|
||||
twoWayA = '?';
|
||||
twoWayB = '?';
|
||||
eventA = new EventEmitter();
|
||||
eventB = new EventEmitter();
|
||||
twoWayAEmitter = new EventEmitter();
|
||||
twoWayBEmitter = new EventEmitter();
|
||||
@Component({
|
||||
selector: 'ng2',
|
||||
inputs: ['literal', 'interpolate', 'oneWayA', 'oneWayB', 'twoWayA', 'twoWayB'],
|
||||
outputs: [
|
||||
'eventA', 'eventB', 'twoWayAEmitter: twoWayAChange', 'twoWayBEmitter: twoWayBChange'
|
||||
],
|
||||
template: 'ignore: {{ignore}}; ' +
|
||||
'literal: {{literal}}; interpolate: {{interpolate}}; ' +
|
||||
'oneWayA: {{oneWayA}}; oneWayB: {{oneWayB}}; ' +
|
||||
'twoWayA: {{twoWayA}}; twoWayB: {{twoWayB}}; ({{ngOnChangesCount}})'
|
||||
})
|
||||
class Ng2Component implements OnChanges {
|
||||
ngOnChangesCount = 0;
|
||||
ignore = '-';
|
||||
literal = '?';
|
||||
interpolate = '?';
|
||||
oneWayA = '?';
|
||||
oneWayB = '?';
|
||||
twoWayA = '?';
|
||||
twoWayB = '?';
|
||||
eventA = new EventEmitter();
|
||||
eventB = new EventEmitter();
|
||||
twoWayAEmitter = new EventEmitter();
|
||||
twoWayBEmitter = new EventEmitter();
|
||||
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
const assert = (prop: string, value: any) => {
|
||||
const propVal = (this as any)[prop];
|
||||
if (propVal != value) {
|
||||
throw new Error(`Expected: '${prop}' to be '${value}' but was '${propVal}'`);
|
||||
}
|
||||
};
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
const assert = (prop: string, value: any) => {
|
||||
const propVal = (this as any)[prop];
|
||||
if (propVal != value) {
|
||||
throw new Error(`Expected: '${prop}' to be '${value}' but was '${propVal}'`);
|
||||
}
|
||||
};
|
||||
|
||||
const assertChange = (prop: string, value: any) => {
|
||||
assert(prop, value);
|
||||
if (!changes[prop]) {
|
||||
throw new Error(`Changes record for '${prop}' not found.`);
|
||||
}
|
||||
const actualValue = changes[prop].currentValue;
|
||||
if (actualValue != value) {
|
||||
throw new Error(
|
||||
`Expected changes record for'${prop}' to be '${value}' but was '${actualValue}'`);
|
||||
}
|
||||
};
|
||||
const assertChange = (prop: string, value: any) => {
|
||||
assert(prop, value);
|
||||
if (!changes[prop]) {
|
||||
throw new Error(`Changes record for '${prop}' not found.`);
|
||||
}
|
||||
const actualValue = changes[prop].currentValue;
|
||||
if (actualValue != value) {
|
||||
throw new Error(
|
||||
`Expected changes record for'${prop}' to be '${value}' but was '${actualValue}'`);
|
||||
}
|
||||
};
|
||||
|
||||
switch (this.ngOnChangesCount++) {
|
||||
case 0:
|
||||
assert('ignore', '-');
|
||||
assertChange('literal', 'Text');
|
||||
assertChange('interpolate', 'Hello world');
|
||||
assertChange('oneWayA', 'A');
|
||||
assertChange('oneWayB', 'B');
|
||||
assertChange('twoWayA', 'initModelA');
|
||||
assertChange('twoWayB', 'initModelB');
|
||||
switch (this.ngOnChangesCount++) {
|
||||
case 0:
|
||||
assert('ignore', '-');
|
||||
assertChange('literal', 'Text');
|
||||
assertChange('interpolate', 'Hello world');
|
||||
assertChange('oneWayA', 'A');
|
||||
assertChange('oneWayB', 'B');
|
||||
assertChange('twoWayA', 'initModelA');
|
||||
assertChange('twoWayB', 'initModelB');
|
||||
|
||||
this.twoWayAEmitter.emit('newA');
|
||||
this.twoWayBEmitter.emit('newB');
|
||||
this.eventA.emit('aFired');
|
||||
this.eventB.emit('bFired');
|
||||
break;
|
||||
case 1:
|
||||
assertChange('twoWayA', 'newA');
|
||||
assertChange('twoWayB', 'newB');
|
||||
break;
|
||||
case 2:
|
||||
assertChange('interpolate', 'Hello everyone');
|
||||
break;
|
||||
default:
|
||||
throw new Error('Called too many times! ' + JSON.stringify(changes));
|
||||
}
|
||||
}
|
||||
}
|
||||
this.twoWayAEmitter.emit('newA');
|
||||
this.twoWayBEmitter.emit('newB');
|
||||
this.eventA.emit('aFired');
|
||||
this.eventB.emit('bFired');
|
||||
break;
|
||||
case 1:
|
||||
assertChange('twoWayA', 'newA');
|
||||
assertChange('twoWayB', 'newB');
|
||||
break;
|
||||
case 2:
|
||||
assertChange('interpolate', 'Hello everyone');
|
||||
break;
|
||||
default:
|
||||
throw new Error('Called too many times! ' + JSON.stringify(changes));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ng1Module.directive('ng2', downgradeComponent({
|
||||
component: Ng2Component,
|
||||
}));
|
||||
ng1Module.directive('ng2', downgradeComponent({
|
||||
component: Ng2Component,
|
||||
}));
|
||||
|
||||
@NgModule({
|
||||
declarations: [Ng2Component],
|
||||
entryComponents: [Ng2Component],
|
||||
imports: [BrowserModule, UpgradeModule]
|
||||
})
|
||||
class Ng2Module {
|
||||
ngDoBootstrap() {}
|
||||
}
|
||||
@NgModule({
|
||||
declarations: [Ng2Component],
|
||||
entryComponents: [Ng2Component],
|
||||
imports: [BrowserModule, UpgradeModule]
|
||||
})
|
||||
class Ng2Module {
|
||||
ngDoBootstrap() {}
|
||||
}
|
||||
|
||||
const element = html(`
|
||||
const element = html(`
|
||||
<div>
|
||||
<ng2 literal="Text" interpolate="Hello {{name}}"
|
||||
bind-one-way-a="dataA" [one-way-b]="dataB"
|
||||
@ -130,23 +128,23 @@ withEachNg1Version(() => {
|
||||
| modelA: {{modelA}}; modelB: {{modelB}}; eventA: {{eventA}}; eventB: {{eventB}};
|
||||
</div>`);
|
||||
|
||||
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then((upgrade) => {
|
||||
expect(multiTrim(document.body.textContent))
|
||||
.toEqual(
|
||||
'ignore: -; ' +
|
||||
'literal: Text; interpolate: Hello world; ' +
|
||||
'oneWayA: A; oneWayB: B; twoWayA: newA; twoWayB: newB; (2) | ' +
|
||||
'modelA: newA; modelB: newB; eventA: aFired; eventB: bFired;');
|
||||
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then((upgrade) => {
|
||||
expect(multiTrim(document.body.textContent))
|
||||
.toEqual(
|
||||
'ignore: -; ' +
|
||||
'literal: Text; interpolate: Hello world; ' +
|
||||
'oneWayA: A; oneWayB: B; twoWayA: newA; twoWayB: newB; (2) | ' +
|
||||
'modelA: newA; modelB: newB; eventA: aFired; eventB: bFired;');
|
||||
|
||||
$apply(upgrade, 'name = "everyone"');
|
||||
expect(multiTrim(document.body.textContent))
|
||||
.toEqual(
|
||||
'ignore: -; ' +
|
||||
'literal: Text; interpolate: Hello everyone; ' +
|
||||
'oneWayA: A; oneWayB: B; twoWayA: newA; twoWayB: newB; (3) | ' +
|
||||
'modelA: newA; modelB: newB; eventA: aFired; eventB: bFired;');
|
||||
});
|
||||
}));
|
||||
$apply(upgrade, 'name = "everyone"');
|
||||
expect(multiTrim(document.body.textContent))
|
||||
.toEqual(
|
||||
'ignore: -; ' +
|
||||
'literal: Text; interpolate: Hello everyone; ' +
|
||||
'oneWayA: A; oneWayB: B; twoWayA: newA; twoWayB: newB; (3) | ' +
|
||||
'modelA: newA; modelB: newB; eventA: aFired; eventB: bFired;');
|
||||
});
|
||||
}));
|
||||
|
||||
it('should bind properties to onpush components', async(() => {
|
||||
const ng1Module = angular.module('ng1', []).run(
|
||||
@ -189,58 +187,57 @@ withEachNg1Version(() => {
|
||||
});
|
||||
}));
|
||||
|
||||
fixmeIvy('FW-715: ngOnChanges being called a second time unexpectedly')
|
||||
.it('should support two-way binding and event listener', async(() => {
|
||||
const listenerSpy = jasmine.createSpy('$rootScope.listener');
|
||||
const ng1Module = angular.module('ng1', []).run(($rootScope: angular.IScope) => {
|
||||
$rootScope['value'] = 'world';
|
||||
$rootScope['listener'] = listenerSpy;
|
||||
});
|
||||
it('should support two-way binding and event listener', async(() => {
|
||||
const listenerSpy = jasmine.createSpy('$rootScope.listener');
|
||||
const ng1Module = angular.module('ng1', []).run(($rootScope: angular.IScope) => {
|
||||
$rootScope['value'] = 'world';
|
||||
$rootScope['listener'] = listenerSpy;
|
||||
});
|
||||
|
||||
@Component({selector: 'ng2', template: `model: {{model}};`})
|
||||
class Ng2Component implements OnChanges {
|
||||
ngOnChangesCount = 0;
|
||||
@Input() model = '?';
|
||||
@Output() modelChange = new EventEmitter();
|
||||
@Component({selector: 'ng2', template: `model: {{model}};`})
|
||||
class Ng2Component implements OnChanges {
|
||||
ngOnChangesCount = 0;
|
||||
@Input() model = '?';
|
||||
@Output() modelChange = new EventEmitter();
|
||||
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
switch (this.ngOnChangesCount++) {
|
||||
case 0:
|
||||
expect(changes.model.currentValue).toBe('world');
|
||||
this.modelChange.emit('newC');
|
||||
break;
|
||||
case 1:
|
||||
expect(changes.model.currentValue).toBe('newC');
|
||||
break;
|
||||
default:
|
||||
throw new Error('Called too many times! ' + JSON.stringify(changes));
|
||||
}
|
||||
}
|
||||
}
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
switch (this.ngOnChangesCount++) {
|
||||
case 0:
|
||||
expect(changes.model.currentValue).toBe('world');
|
||||
this.modelChange.emit('newC');
|
||||
break;
|
||||
case 1:
|
||||
expect(changes.model.currentValue).toBe('newC');
|
||||
break;
|
||||
default:
|
||||
throw new Error('Called too many times! ' + JSON.stringify(changes));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ng1Module.directive('ng2', downgradeComponent({component: Ng2Component}));
|
||||
ng1Module.directive('ng2', downgradeComponent({component: Ng2Component}));
|
||||
|
||||
@NgModule({
|
||||
declarations: [Ng2Component],
|
||||
entryComponents: [Ng2Component],
|
||||
imports: [BrowserModule, UpgradeModule]
|
||||
})
|
||||
class Ng2Module {
|
||||
ngDoBootstrap() {}
|
||||
}
|
||||
@NgModule({
|
||||
declarations: [Ng2Component],
|
||||
entryComponents: [Ng2Component],
|
||||
imports: [BrowserModule, UpgradeModule]
|
||||
})
|
||||
class Ng2Module {
|
||||
ngDoBootstrap() {}
|
||||
}
|
||||
|
||||
const element = html(`
|
||||
const element = html(`
|
||||
<div>
|
||||
<ng2 [(model)]="value" (model-change)="listener($event)"></ng2>
|
||||
| value: {{value}}
|
||||
</div>
|
||||
`);
|
||||
|
||||
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then((upgrade) => {
|
||||
expect(multiTrim(element.textContent)).toEqual('model: newC; | value: newC');
|
||||
expect(listenerSpy).toHaveBeenCalledWith('newC');
|
||||
});
|
||||
}));
|
||||
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then((upgrade) => {
|
||||
expect(multiTrim(element.textContent)).toEqual('model: newC; | value: newC');
|
||||
expect(listenerSpy).toHaveBeenCalledWith('newC');
|
||||
});
|
||||
}));
|
||||
|
||||
it('should run change-detection on every digest (by default)', async(() => {
|
||||
let ng2Component: Ng2Component;
|
||||
@ -404,66 +401,65 @@ withEachNg1Version(() => {
|
||||
});
|
||||
}));
|
||||
|
||||
fixmeIvy('FW-715: ngOnChanges being called a second time unexpectedly')
|
||||
.it('should initialize inputs in time for `ngOnChanges`', async(() => {
|
||||
@Component({
|
||||
selector: 'ng2',
|
||||
template: `
|
||||
it('should initialize inputs in time for `ngOnChanges`', async(() => {
|
||||
@Component({
|
||||
selector: 'ng2',
|
||||
template: `
|
||||
ngOnChangesCount: {{ ngOnChangesCount }} |
|
||||
firstChangesCount: {{ firstChangesCount }} |
|
||||
initialValue: {{ initialValue }}`
|
||||
})
|
||||
class Ng2Component implements OnChanges {
|
||||
ngOnChangesCount = 0;
|
||||
firstChangesCount = 0;
|
||||
// TODO(issue/24571): remove '!'.
|
||||
initialValue !: string;
|
||||
// TODO(issue/24571): remove '!'.
|
||||
@Input() foo !: string;
|
||||
})
|
||||
class Ng2Component implements OnChanges {
|
||||
ngOnChangesCount = 0;
|
||||
firstChangesCount = 0;
|
||||
// TODO(issue/24571): remove '!'.
|
||||
initialValue !: string;
|
||||
// TODO(issue/24571): remove '!'.
|
||||
@Input() foo !: string;
|
||||
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
this.ngOnChangesCount++;
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
this.ngOnChangesCount++;
|
||||
|
||||
if (this.ngOnChangesCount === 1) {
|
||||
this.initialValue = this.foo;
|
||||
}
|
||||
if (this.ngOnChangesCount === 1) {
|
||||
this.initialValue = this.foo;
|
||||
}
|
||||
|
||||
if (changes['foo'] && changes['foo'].isFirstChange()) {
|
||||
this.firstChangesCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (changes['foo'] && changes['foo'].isFirstChange()) {
|
||||
this.firstChangesCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
imports: [BrowserModule, UpgradeModule],
|
||||
declarations: [Ng2Component],
|
||||
entryComponents: [Ng2Component]
|
||||
})
|
||||
class Ng2Module {
|
||||
ngDoBootstrap() {}
|
||||
}
|
||||
@NgModule({
|
||||
imports: [BrowserModule, UpgradeModule],
|
||||
declarations: [Ng2Component],
|
||||
entryComponents: [Ng2Component]
|
||||
})
|
||||
class Ng2Module {
|
||||
ngDoBootstrap() {}
|
||||
}
|
||||
|
||||
const ng1Module = angular.module('ng1', []).directive(
|
||||
'ng2', downgradeComponent({component: Ng2Component}));
|
||||
const ng1Module = angular.module('ng1', []).directive(
|
||||
'ng2', downgradeComponent({component: Ng2Component}));
|
||||
|
||||
const element = html(`
|
||||
const element = html(`
|
||||
<ng2 [foo]="'foo'"></ng2>
|
||||
<ng2 foo="bar"></ng2>
|
||||
<ng2 [foo]="'baz'" ng-if="true"></ng2>
|
||||
<ng2 foo="qux" ng-if="true"></ng2>
|
||||
`);
|
||||
|
||||
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(upgrade => {
|
||||
const nodes = element.querySelectorAll('ng2');
|
||||
const expectedTextWith = (value: string) =>
|
||||
`ngOnChangesCount: 1 | firstChangesCount: 1 | initialValue: ${value}`;
|
||||
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(upgrade => {
|
||||
const nodes = element.querySelectorAll('ng2');
|
||||
const expectedTextWith = (value: string) =>
|
||||
`ngOnChangesCount: 1 | firstChangesCount: 1 | initialValue: ${value}`;
|
||||
|
||||
expect(multiTrim(nodes[0].textContent)).toBe(expectedTextWith('foo'));
|
||||
expect(multiTrim(nodes[1].textContent)).toBe(expectedTextWith('bar'));
|
||||
expect(multiTrim(nodes[2].textContent)).toBe(expectedTextWith('baz'));
|
||||
expect(multiTrim(nodes[3].textContent)).toBe(expectedTextWith('qux'));
|
||||
});
|
||||
}));
|
||||
expect(multiTrim(nodes[0].textContent)).toBe(expectedTextWith('foo'));
|
||||
expect(multiTrim(nodes[1].textContent)).toBe(expectedTextWith('bar'));
|
||||
expect(multiTrim(nodes[2].textContent)).toBe(expectedTextWith('baz'));
|
||||
expect(multiTrim(nodes[3].textContent)).toBe(expectedTextWith('qux'));
|
||||
});
|
||||
}));
|
||||
|
||||
it('should bind to ng-model', async(() => {
|
||||
const ng1Module = angular.module('ng1', []).run(
|
||||
|
@ -721,66 +721,63 @@ withEachNg1Version(() => {
|
||||
});
|
||||
}));
|
||||
|
||||
fixmeIvy('FW-715: ngOnChanges being called a second time unexpectedly')
|
||||
.it('should propagate input changes inside the Angular zone', async(() => {
|
||||
let ng2Component: Ng2Component;
|
||||
it('should propagate input changes inside the Angular zone', async(() => {
|
||||
let ng2Component: Ng2Component;
|
||||
|
||||
@Component({selector: 'ng2', template: ''})
|
||||
class Ng2Component implements OnChanges {
|
||||
@Input() attrInput = 'foo';
|
||||
@Input() propInput = 'foo';
|
||||
@Component({selector: 'ng2', template: ''})
|
||||
class Ng2Component implements OnChanges {
|
||||
@Input() attrInput = 'foo';
|
||||
@Input() propInput = 'foo';
|
||||
|
||||
constructor() { ng2Component = this; }
|
||||
ngOnChanges() {}
|
||||
}
|
||||
constructor() { ng2Component = this; }
|
||||
ngOnChanges() {}
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [Ng2Component],
|
||||
entryComponents: [Ng2Component],
|
||||
imports: [BrowserModule],
|
||||
})
|
||||
class Ng2Module {
|
||||
ngDoBootstrap() {}
|
||||
}
|
||||
@NgModule({
|
||||
declarations: [Ng2Component],
|
||||
entryComponents: [Ng2Component],
|
||||
imports: [BrowserModule],
|
||||
})
|
||||
class Ng2Module {
|
||||
ngDoBootstrap() {}
|
||||
}
|
||||
|
||||
const bootstrapFn = (extraProviders: StaticProvider[]) =>
|
||||
platformBrowserDynamic(extraProviders).bootstrapModule(Ng2Module);
|
||||
const lazyModuleName = downgradeModule<Ng2Module>(bootstrapFn);
|
||||
const ng1Module =
|
||||
angular.module('ng1', [lazyModuleName])
|
||||
.directive(
|
||||
'ng2', downgradeComponent({component: Ng2Component, propagateDigest}))
|
||||
.run(($rootScope: angular.IRootScopeService) => {
|
||||
$rootScope.attrVal = 'bar';
|
||||
$rootScope.propVal = 'bar';
|
||||
});
|
||||
const bootstrapFn = (extraProviders: StaticProvider[]) =>
|
||||
platformBrowserDynamic(extraProviders).bootstrapModule(Ng2Module);
|
||||
const lazyModuleName = downgradeModule<Ng2Module>(bootstrapFn);
|
||||
const ng1Module =
|
||||
angular.module('ng1', [lazyModuleName])
|
||||
.directive('ng2', downgradeComponent({component: Ng2Component, propagateDigest}))
|
||||
.run(($rootScope: angular.IRootScopeService) => {
|
||||
$rootScope.attrVal = 'bar';
|
||||
$rootScope.propVal = 'bar';
|
||||
});
|
||||
|
||||
const element =
|
||||
html('<ng2 attr-input="{{ attrVal }}" [prop-input]="propVal"></ng2>');
|
||||
const $injector = angular.bootstrap(element, [ng1Module.name]);
|
||||
const $rootScope = $injector.get($ROOT_SCOPE) as angular.IRootScopeService;
|
||||
const element = html('<ng2 attr-input="{{ attrVal }}" [prop-input]="propVal"></ng2>');
|
||||
const $injector = angular.bootstrap(element, [ng1Module.name]);
|
||||
const $rootScope = $injector.get($ROOT_SCOPE) as angular.IRootScopeService;
|
||||
|
||||
setTimeout(() => { // Wait for the module to be bootstrapped.
|
||||
setTimeout(() => { // Wait for `$evalAsync()` to propagate inputs.
|
||||
const expectToBeInNgZone = () => expect(NgZone.isInAngularZone()).toBe(true);
|
||||
const changesSpy =
|
||||
spyOn(ng2Component, 'ngOnChanges').and.callFake(expectToBeInNgZone);
|
||||
setTimeout(() => { // Wait for the module to be bootstrapped.
|
||||
setTimeout(() => { // Wait for `$evalAsync()` to propagate inputs.
|
||||
const expectToBeInNgZone = () => expect(NgZone.isInAngularZone()).toBe(true);
|
||||
const changesSpy =
|
||||
spyOn(ng2Component, 'ngOnChanges').and.callFake(expectToBeInNgZone);
|
||||
|
||||
expect(ng2Component.attrInput).toBe('bar');
|
||||
expect(ng2Component.propInput).toBe('bar');
|
||||
expect(ng2Component.attrInput).toBe('bar');
|
||||
expect(ng2Component.propInput).toBe('bar');
|
||||
|
||||
$rootScope.$apply('attrVal = "baz"');
|
||||
expect(ng2Component.attrInput).toBe('baz');
|
||||
expect(ng2Component.propInput).toBe('bar');
|
||||
expect(changesSpy).toHaveBeenCalledTimes(1);
|
||||
$rootScope.$apply('attrVal = "baz"');
|
||||
expect(ng2Component.attrInput).toBe('baz');
|
||||
expect(ng2Component.propInput).toBe('bar');
|
||||
expect(changesSpy).toHaveBeenCalledTimes(1);
|
||||
|
||||
$rootScope.$apply('propVal = "qux"');
|
||||
expect(ng2Component.attrInput).toBe('baz');
|
||||
expect(ng2Component.propInput).toBe('qux');
|
||||
expect(changesSpy).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
}));
|
||||
$rootScope.$apply('propVal = "qux"');
|
||||
expect(ng2Component.attrInput).toBe('baz');
|
||||
expect(ng2Component.propInput).toBe('qux');
|
||||
expect(changesSpy).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
}));
|
||||
|
||||
it('should create and destroy nested, asynchronously instantiated components inside the Angular zone',
|
||||
async(() => {
|
||||
@ -943,167 +940,165 @@ withEachNg1Version(() => {
|
||||
});
|
||||
}));
|
||||
|
||||
fixmeIvy('FW-715: ngOnChanges being called a second time unexpectedly')
|
||||
.it('should run the lifecycle hooks in the correct order', async(() => {
|
||||
const logs: string[] = [];
|
||||
let rootScope: angular.IRootScopeService;
|
||||
it('should run the lifecycle hooks in the correct order', async(() => {
|
||||
const logs: string[] = [];
|
||||
let rootScope: angular.IRootScopeService;
|
||||
|
||||
@Component({
|
||||
selector: 'ng2',
|
||||
template: `
|
||||
@Component({
|
||||
selector: 'ng2',
|
||||
template: `
|
||||
{{ value }}
|
||||
<button (click)="value = 'qux'"></button>
|
||||
<ng-content></ng-content>
|
||||
`
|
||||
})
|
||||
class Ng2Component implements AfterContentChecked,
|
||||
AfterContentInit, AfterViewChecked, AfterViewInit, DoCheck, OnChanges,
|
||||
OnDestroy, OnInit {
|
||||
@Input() value = 'foo';
|
||||
})
|
||||
class Ng2Component implements AfterContentChecked,
|
||||
AfterContentInit, AfterViewChecked, AfterViewInit, DoCheck, OnChanges, OnDestroy,
|
||||
OnInit {
|
||||
@Input() value = 'foo';
|
||||
|
||||
ngAfterContentChecked() { this.log('AfterContentChecked'); }
|
||||
ngAfterContentInit() { this.log('AfterContentInit'); }
|
||||
ngAfterViewChecked() { this.log('AfterViewChecked'); }
|
||||
ngAfterViewInit() { this.log('AfterViewInit'); }
|
||||
ngDoCheck() { this.log('DoCheck'); }
|
||||
ngOnChanges() { this.log('OnChanges'); }
|
||||
ngOnDestroy() { this.log('OnDestroy'); }
|
||||
ngOnInit() { this.log('OnInit'); }
|
||||
ngAfterContentChecked() { this.log('AfterContentChecked'); }
|
||||
ngAfterContentInit() { this.log('AfterContentInit'); }
|
||||
ngAfterViewChecked() { this.log('AfterViewChecked'); }
|
||||
ngAfterViewInit() { this.log('AfterViewInit'); }
|
||||
ngDoCheck() { this.log('DoCheck'); }
|
||||
ngOnChanges() { this.log('OnChanges'); }
|
||||
ngOnDestroy() { this.log('OnDestroy'); }
|
||||
ngOnInit() { this.log('OnInit'); }
|
||||
|
||||
private log(hook: string) { logs.push(`${hook}(${this.value})`); }
|
||||
}
|
||||
private log(hook: string) { logs.push(`${hook}(${this.value})`); }
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [Ng2Component],
|
||||
entryComponents: [Ng2Component],
|
||||
imports: [BrowserModule],
|
||||
})
|
||||
class Ng2Module {
|
||||
ngDoBootstrap() {}
|
||||
}
|
||||
@NgModule({
|
||||
declarations: [Ng2Component],
|
||||
entryComponents: [Ng2Component],
|
||||
imports: [BrowserModule],
|
||||
})
|
||||
class Ng2Module {
|
||||
ngDoBootstrap() {}
|
||||
}
|
||||
|
||||
const bootstrapFn = (extraProviders: StaticProvider[]) =>
|
||||
platformBrowserDynamic(extraProviders).bootstrapModule(Ng2Module);
|
||||
const lazyModuleName = downgradeModule<Ng2Module>(bootstrapFn);
|
||||
const ng1Module =
|
||||
angular.module('ng1', [lazyModuleName])
|
||||
.directive(
|
||||
'ng2', downgradeComponent({component: Ng2Component, propagateDigest}))
|
||||
.run(($rootScope: angular.IRootScopeService) => {
|
||||
rootScope = $rootScope;
|
||||
rootScope.value = 'bar';
|
||||
});
|
||||
const bootstrapFn = (extraProviders: StaticProvider[]) =>
|
||||
platformBrowserDynamic(extraProviders).bootstrapModule(Ng2Module);
|
||||
const lazyModuleName = downgradeModule<Ng2Module>(bootstrapFn);
|
||||
const ng1Module =
|
||||
angular.module('ng1', [lazyModuleName])
|
||||
.directive('ng2', downgradeComponent({component: Ng2Component, propagateDigest}))
|
||||
.run(($rootScope: angular.IRootScopeService) => {
|
||||
rootScope = $rootScope;
|
||||
rootScope.value = 'bar';
|
||||
});
|
||||
|
||||
const element =
|
||||
html('<div><ng2 value="{{ value }}" ng-if="!hideNg2">Content</ng2></div>');
|
||||
angular.bootstrap(element, [ng1Module.name]);
|
||||
const element =
|
||||
html('<div><ng2 value="{{ value }}" ng-if="!hideNg2">Content</ng2></div>');
|
||||
angular.bootstrap(element, [ng1Module.name]);
|
||||
|
||||
setTimeout(() => { // Wait for the module to be bootstrapped.
|
||||
setTimeout(() => { // Wait for `$evalAsync()` to propagate inputs.
|
||||
const button = element.querySelector('button') !;
|
||||
setTimeout(() => { // Wait for the module to be bootstrapped.
|
||||
setTimeout(() => { // Wait for `$evalAsync()` to propagate inputs.
|
||||
const button = element.querySelector('button') !;
|
||||
|
||||
// Once initialized.
|
||||
expect(multiTrim(element.textContent)).toBe('bar Content');
|
||||
expect(logs).toEqual([
|
||||
// `ngOnChanges()` call triggered directly through the `inputChanges`
|
||||
// $watcher.
|
||||
'OnChanges(bar)',
|
||||
// Initial CD triggered directly through the `detectChanges()` or
|
||||
// `inputChanges`
|
||||
// $watcher (for `propagateDigest` true/false respectively).
|
||||
'OnInit(bar)',
|
||||
'DoCheck(bar)',
|
||||
'AfterContentInit(bar)',
|
||||
'AfterContentChecked(bar)',
|
||||
'AfterViewInit(bar)',
|
||||
'AfterViewChecked(bar)',
|
||||
...(propagateDigest ?
|
||||
[
|
||||
// CD triggered directly through the `detectChanges()` $watcher (2nd
|
||||
// $digest).
|
||||
'DoCheck(bar)',
|
||||
'AfterContentChecked(bar)',
|
||||
'AfterViewChecked(bar)',
|
||||
] :
|
||||
[]),
|
||||
// CD triggered due to entering/leaving the NgZone (in `downgradeFn()`).
|
||||
'DoCheck(bar)',
|
||||
'AfterContentChecked(bar)',
|
||||
'AfterViewChecked(bar)',
|
||||
]);
|
||||
logs.length = 0;
|
||||
// Once initialized.
|
||||
expect(multiTrim(element.textContent)).toBe('bar Content');
|
||||
expect(logs).toEqual([
|
||||
// `ngOnChanges()` call triggered directly through the `inputChanges`
|
||||
// $watcher.
|
||||
'OnChanges(bar)',
|
||||
// Initial CD triggered directly through the `detectChanges()` or
|
||||
// `inputChanges`
|
||||
// $watcher (for `propagateDigest` true/false respectively).
|
||||
'OnInit(bar)',
|
||||
'DoCheck(bar)',
|
||||
'AfterContentInit(bar)',
|
||||
'AfterContentChecked(bar)',
|
||||
'AfterViewInit(bar)',
|
||||
'AfterViewChecked(bar)',
|
||||
...(propagateDigest ?
|
||||
[
|
||||
// CD triggered directly through the `detectChanges()` $watcher (2nd
|
||||
// $digest).
|
||||
'DoCheck(bar)',
|
||||
'AfterContentChecked(bar)',
|
||||
'AfterViewChecked(bar)',
|
||||
] :
|
||||
[]),
|
||||
// CD triggered due to entering/leaving the NgZone (in `downgradeFn()`).
|
||||
'DoCheck(bar)',
|
||||
'AfterContentChecked(bar)',
|
||||
'AfterViewChecked(bar)',
|
||||
]);
|
||||
logs.length = 0;
|
||||
|
||||
// Change inputs and run `$digest`.
|
||||
rootScope.$apply('value = "baz"');
|
||||
expect(multiTrim(element.textContent)).toBe('baz Content');
|
||||
expect(logs).toEqual([
|
||||
// `ngOnChanges()` call triggered directly through the `inputChanges`
|
||||
// $watcher.
|
||||
'OnChanges(baz)',
|
||||
// `propagateDigest: true` (3 CD runs):
|
||||
// - CD triggered due to entering/leaving the NgZone (in `inputChanges`
|
||||
// $watcher).
|
||||
// - CD triggered directly through the `detectChanges()` $watcher.
|
||||
// - CD triggered due to entering/leaving the NgZone (in `detectChanges`
|
||||
// $watcher).
|
||||
// `propagateDigest: false` (2 CD runs):
|
||||
// - CD triggered directly through the `inputChanges` $watcher.
|
||||
// - CD triggered due to entering/leaving the NgZone (in `inputChanges`
|
||||
// $watcher).
|
||||
'DoCheck(baz)',
|
||||
'AfterContentChecked(baz)',
|
||||
'AfterViewChecked(baz)',
|
||||
'DoCheck(baz)',
|
||||
'AfterContentChecked(baz)',
|
||||
'AfterViewChecked(baz)',
|
||||
...(propagateDigest ?
|
||||
[
|
||||
'DoCheck(baz)',
|
||||
'AfterContentChecked(baz)',
|
||||
'AfterViewChecked(baz)',
|
||||
] :
|
||||
[]),
|
||||
]);
|
||||
logs.length = 0;
|
||||
// Change inputs and run `$digest`.
|
||||
rootScope.$apply('value = "baz"');
|
||||
expect(multiTrim(element.textContent)).toBe('baz Content');
|
||||
expect(logs).toEqual([
|
||||
// `ngOnChanges()` call triggered directly through the `inputChanges`
|
||||
// $watcher.
|
||||
'OnChanges(baz)',
|
||||
// `propagateDigest: true` (3 CD runs):
|
||||
// - CD triggered due to entering/leaving the NgZone (in `inputChanges`
|
||||
// $watcher).
|
||||
// - CD triggered directly through the `detectChanges()` $watcher.
|
||||
// - CD triggered due to entering/leaving the NgZone (in `detectChanges`
|
||||
// $watcher).
|
||||
// `propagateDigest: false` (2 CD runs):
|
||||
// - CD triggered directly through the `inputChanges` $watcher.
|
||||
// - CD triggered due to entering/leaving the NgZone (in `inputChanges`
|
||||
// $watcher).
|
||||
'DoCheck(baz)',
|
||||
'AfterContentChecked(baz)',
|
||||
'AfterViewChecked(baz)',
|
||||
'DoCheck(baz)',
|
||||
'AfterContentChecked(baz)',
|
||||
'AfterViewChecked(baz)',
|
||||
...(propagateDigest ?
|
||||
[
|
||||
'DoCheck(baz)',
|
||||
'AfterContentChecked(baz)',
|
||||
'AfterViewChecked(baz)',
|
||||
] :
|
||||
[]),
|
||||
]);
|
||||
logs.length = 0;
|
||||
|
||||
// Run `$digest` (without changing inputs).
|
||||
rootScope.$digest();
|
||||
expect(multiTrim(element.textContent)).toBe('baz Content');
|
||||
expect(logs).toEqual(
|
||||
propagateDigest ?
|
||||
[
|
||||
// CD triggered directly through the `detectChanges()` $watcher.
|
||||
'DoCheck(baz)',
|
||||
'AfterContentChecked(baz)',
|
||||
'AfterViewChecked(baz)',
|
||||
// CD triggered due to entering/leaving the NgZone (in the above
|
||||
// $watcher).
|
||||
'DoCheck(baz)',
|
||||
'AfterContentChecked(baz)',
|
||||
'AfterViewChecked(baz)',
|
||||
] :
|
||||
[]);
|
||||
logs.length = 0;
|
||||
// Run `$digest` (without changing inputs).
|
||||
rootScope.$digest();
|
||||
expect(multiTrim(element.textContent)).toBe('baz Content');
|
||||
expect(logs).toEqual(
|
||||
propagateDigest ?
|
||||
[
|
||||
// CD triggered directly through the `detectChanges()` $watcher.
|
||||
'DoCheck(baz)',
|
||||
'AfterContentChecked(baz)',
|
||||
'AfterViewChecked(baz)',
|
||||
// CD triggered due to entering/leaving the NgZone (in the above
|
||||
// $watcher).
|
||||
'DoCheck(baz)',
|
||||
'AfterContentChecked(baz)',
|
||||
'AfterViewChecked(baz)',
|
||||
] :
|
||||
[]);
|
||||
logs.length = 0;
|
||||
|
||||
// Trigger change detection (without changing inputs).
|
||||
button.click();
|
||||
expect(multiTrim(element.textContent)).toBe('qux Content');
|
||||
expect(logs).toEqual([
|
||||
'DoCheck(qux)',
|
||||
'AfterContentChecked(qux)',
|
||||
'AfterViewChecked(qux)',
|
||||
]);
|
||||
logs.length = 0;
|
||||
// Trigger change detection (without changing inputs).
|
||||
button.click();
|
||||
expect(multiTrim(element.textContent)).toBe('qux Content');
|
||||
expect(logs).toEqual([
|
||||
'DoCheck(qux)',
|
||||
'AfterContentChecked(qux)',
|
||||
'AfterViewChecked(qux)',
|
||||
]);
|
||||
logs.length = 0;
|
||||
|
||||
// Destroy the component.
|
||||
rootScope.$apply('hideNg2 = true');
|
||||
expect(logs).toEqual([
|
||||
'OnDestroy(qux)',
|
||||
]);
|
||||
logs.length = 0;
|
||||
});
|
||||
});
|
||||
}));
|
||||
// Destroy the component.
|
||||
rootScope.$apply('hideNg2 = true');
|
||||
expect(logs).toEqual([
|
||||
'OnDestroy(qux)',
|
||||
]);
|
||||
logs.length = 0;
|
||||
});
|
||||
});
|
||||
}));
|
||||
|
||||
it('should detach hostViews from the ApplicationRef once destroyed', async(() => {
|
||||
let ng2Component: Ng2Component;
|
||||
|
Reference in New Issue
Block a user