fix(ivy): fix property names of ngOnChanges (#27714)

- #reslove FW-812
- #reslove FW-844

PR Close #27714
This commit is contained in:
Misko Hevery
2018-12-17 13:17:42 -08:00
committed by Miško Hevery
parent 4774a1abff
commit 1c93afe956
8 changed files with 237 additions and 217 deletions

View File

@ -110,7 +110,7 @@ function baseDirectiveFields(
meta, elVarExp, contextVarExp, styleBuilder, bindingParser, constantPool, hostVarsCount));
// e.g 'inputs: {a: 'a'}`
definitionMap.set('inputs', conditionallyCreateMapObjectLiteral(meta.inputs));
definitionMap.set('inputs', conditionallyCreateMapObjectLiteral(meta.inputs, true));
// e.g 'outputs: {a: 'a'}`
definitionMap.set('outputs', conditionallyCreateMapObjectLiteral(meta.outputs));

View File

@ -67,20 +67,36 @@ export function asLiteral(value: any): o.Expression {
return o.literal(value, o.INFERRED_TYPE);
}
export function conditionallyCreateMapObjectLiteral(keys: {[key: string]: string | string[]}):
o.Expression|null {
export function conditionallyCreateMapObjectLiteral(
keys: {[key: string]: string | string[]}, keepDeclared?: boolean): o.Expression|null {
if (Object.getOwnPropertyNames(keys).length > 0) {
return mapToExpression(keys);
return mapToExpression(keys, keepDeclared);
}
return null;
}
function mapToExpression(map: {[key: string]: any}): o.Expression {
function mapToExpression(
map: {[key: string]: string | string[]}, keepDeclared?: boolean): o.Expression {
return o.literalMap(Object.getOwnPropertyNames(map).map(key => {
// canonical syntax: `dirProp: elProp`
// canonical syntax: `dirProp: publicProp`
// if there is no `:`, use dirProp = elProp
const parts = splitAtColon(key, [key, map[key]]);
return {key: parts[0], quoted: false, value: asLiteral(parts[1])};
const value = map[key];
let declaredName: string;
let publicName: string;
let minifiedName: string;
if (Array.isArray(value)) {
[publicName, declaredName] = value;
} else {
[declaredName, publicName] = splitAtColon(key, [key, value]);
}
minifiedName = declaredName;
return {
key: minifiedName,
quoted: false,
value: (keepDeclared && publicName !== declaredName) ?
o.literalArr([asLiteral(publicName), asLiteral(declaredName)]) :
asLiteral(publicName)
};
}));
}

View File

@ -345,8 +345,8 @@ export function defineNgModule<T>(def: {type: T} & Partial<NgModuleDef<T>>): nev
* @Input()
* propName1: string;
*
* @Input('publicName')
* propName2: number;
* @Input('publicName2')
* declaredPropName2: number;
* }
* ```
*
@ -354,26 +354,35 @@ export function defineNgModule<T>(def: {type: T} & Partial<NgModuleDef<T>>): nev
*
* ```
* {
* a0: 'propName1',
* b1: ['publicName', 'propName2'],
* propName1: 'propName1',
* declaredPropName2: ['publicName2', 'declaredPropName2'],
* }
* ```
*
* becomes
* which is than translated by the minifier as:
*
* ```
* {
* 'propName1': 'a0',
* 'publicName': 'b1'
* minifiedPropName1: 'propName1',
* minifiedPropName2: ['publicName2', 'declaredPropName2'],
* }
* ```
*
* Optionally the function can take `secondary` which will result in:
* becomes: (public name => minifiedName)
*
* ```
* {
* 'propName1': 'a0',
* 'propName2': 'b1'
* 'propName1': 'minifiedPropName1',
* 'publicName2': 'minifiedPropName2',
* }
* ```
*
* Optionally the function can take `secondary` which will result in: (public name => declared name)
*
* ```
* {
* 'propName1': 'propName1',
* 'publicName2': 'declaredPropName2',
* }
* ```
*
@ -384,7 +393,7 @@ function invertObject(obj: any, secondary?: any): any {
const newLookup: any = {};
for (const minifiedKey in obj) {
if (obj.hasOwnProperty(minifiedKey)) {
let publicName = obj[minifiedKey];
let publicName: string = obj[minifiedKey];
let declaredName = publicName;
if (Array.isArray(publicName)) {
declaredName = publicName[1];
@ -392,7 +401,7 @@ function invertObject(obj: any, secondary?: any): any {
}
newLookup[publicName] = minifiedKey;
if (secondary) {
(secondary[declaredName] = minifiedKey);
(secondary[publicName] = declaredName);
}
}
}

View File

@ -39,11 +39,13 @@ type OnChangesExpando = OnChanges & {
* ```
*/
export function NgOnChangesFeature<T>(definition: DirectiveDef<T>): void {
const declaredToMinifiedInputs = definition.declaredInputs;
const publicToDeclaredInputs = definition.declaredInputs;
const publicToMinifiedInputs = definition.inputs;
const proto = definition.type.prototype;
for (const declaredName in declaredToMinifiedInputs) {
if (declaredToMinifiedInputs.hasOwnProperty(declaredName)) {
const minifiedKey = declaredToMinifiedInputs[declaredName];
for (const publicName in publicToDeclaredInputs) {
if (publicToDeclaredInputs.hasOwnProperty(publicName)) {
const minifiedKey = publicToMinifiedInputs[publicName];
const declaredKey = publicToDeclaredInputs[publicName];
const privateMinKey = PRIVATE_PREFIX + minifiedKey;
// Walk the prototype chain to see if we find a property descriptor
@ -72,12 +74,12 @@ export function NgOnChangesFeature<T>(definition: DirectiveDef<T>): void {
}
const isFirstChange = !this.hasOwnProperty(privateMinKey);
const currentChange = simpleChanges[declaredName];
const currentChange = simpleChanges[declaredKey];
if (currentChange) {
currentChange.currentValue = value;
} else {
simpleChanges[declaredName] =
simpleChanges[declaredKey] =
new SimpleChange(this[privateMinKey], value, isFirstChange);
}

View File

@ -84,10 +84,10 @@ describe('InheritDefinitionFeature', () => {
qux: 'subQux',
});
expect(subDef.declaredInputs).toEqual({
declaredFoo: 'superFoo',
bar: 'superBar',
baz: 'subBaz',
qux: 'subQux',
foo: 'declaredFoo',
bar: 'bar',
baz: 'baz',
qux: 'qux',
});
});
@ -228,7 +228,7 @@ describe('InheritDefinitionFeature', () => {
expect(subDef.declaredInputs).toEqual({
input1: 'input1',
input2: 'input2',
input3: 'input3',
alias3: 'input3',
input4: 'input4',
input5: 'input5',
});

View File

@ -264,7 +264,7 @@ ivyEnabled && describe('render3 jit', () => {
const InputCompAny = InputComp as any;
expect(InputCompAny.ngComponentDef.inputs).toEqual({publicName: 'privateName'});
expect(InputCompAny.ngComponentDef.declaredInputs).toEqual({privateName: 'privateName'});
expect(InputCompAny.ngComponentDef.declaredInputs).toEqual({publicName: 'privateName'});
});
it('should add @Input properties to a directive', () => {
@ -277,7 +277,7 @@ ivyEnabled && describe('render3 jit', () => {
const InputDirAny = InputDir as any;
expect(InputDirAny.ngDirectiveDef.inputs).toEqual({publicName: 'privateName'});
expect(InputDirAny.ngDirectiveDef.declaredInputs).toEqual({privateName: 'privateName'});
expect(InputDirAny.ngDirectiveDef.declaredInputs).toEqual({publicName: 'privateName'});
});
it('should add ngBaseDef to types with @Input properties', () => {

View File

@ -1915,9 +1915,7 @@ withEachNg1Version(() => {
});
}));
fixmeIvy(
'FW-844: Directive input bindings cannot be assigned after the `@Directive` decorator has been compiled')
.it('should call `$onChanges()` on binding destination', fakeAsync(() => {
it('should call `$onChanges()` on binding destination', fakeAsync(() => {
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
const $onChangesControllerSpyA = jasmine.createSpy('$onChangesControllerA');
const $onChangesControllerSpyB = jasmine.createSpy('$onChangesControllerB');
@ -1942,15 +1940,15 @@ withEachNg1Version(() => {
this.$onChanges = $onChangesControllerSpyA;
}
}))
.directive('ng1B', () => ({
.directive(
'ng1B',
() => ({
template: '',
scope: {valB: '<'},
bindToController: false,
controllerAs: '$ctrl',
controller: class {
$onChanges(changes: SimpleChanges) {
$onChangesControllerSpyB(changes);
}
$onChanges(changes: SimpleChanges) { $onChangesControllerSpyB(changes); }
}
}))
.directive('ng2', adapter.downgradeNg2Component(Ng2Component))
@ -2011,9 +2009,7 @@ withEachNg1Version(() => {
});
}));
fixmeIvy(
'FW-843: destroy hooks are not registered on upgraded ng1 components contained in ng2 component templates under ivy')
.it('should call `$onDestroy()` on controller', fakeAsync(() => {
it('should call `$onDestroy()` on controller', fakeAsync(() => {
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
const $onDestroySpyA = jasmine.createSpy('$onDestroyA');
const $onDestroySpyB = jasmine.createSpy('$onDestroyB');
@ -2045,8 +2041,7 @@ withEachNg1Version(() => {
controllerAs: '$ctrl',
controller: class {$onDestroy() { $onDestroySpyA(); }}
}))
.directive(
'ng1B', () => ({
.directive('ng1B', () => ({
template: '',
scope: {},
bindToController: false,
@ -3053,16 +3048,13 @@ withEachNg1Version(() => {
}));
});
fixmeIvy(
'FW-844: Directive input bindings cannot be assigned after the `@Directive` decorator has been compiled')
.it('should bind input properties (<) of components', async(() => {
it('should bind input properties (<) of components', async(() => {
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
const ng1Module = angular.module('ng1', []);
const ng1 = {
bindings: {personProfile: '<'},
template:
'Hello {{$ctrl.personProfile.firstName}} {{$ctrl.personProfile.lastName}}',
template: 'Hello {{$ctrl.personProfile.firstName}} {{$ctrl.personProfile.lastName}}',
controller: class {}
};
ng1Module.component('ng1', ng1);

View File

@ -3968,6 +3968,7 @@ withEachNg1Version(() => {
});
}));
// fixmeIvy('FW-724: upgraded ng1 components are not being rendered')
it('should support ng2 > ng1 > ng2 (with inputs/outputs)', fakeAsync(() => {
let ng2ComponentAInstance: Ng2ComponentA;
let ng2ComponentBInstance: Ng2ComponentB;