fix(upgrade): use correct attribute name for upgraded component's bindings (#16128)

Previously, when using a different property/attribute name for an upgraded
component's binding (e.g. `bindings: {propName: '<attrName'}`), the property and
attribute names were swapped (e.g. using `attrName` as the property name and
`propName` as the attribute name). This resulted in unexpected behavior.

This commit fixes this using the correct names for properties and attributes.

This only affects `upgrade/dynamic`. `upgrade/static` works correctly already.

Fixes #8856

PR Close #16128
This commit is contained in:
Georgios Kalpakas
2017-04-18 16:49:35 +03:00
committed by Miško Hevery
parent 2f977312be
commit d1fb066d07
3 changed files with 317 additions and 65 deletions

View File

@ -101,49 +101,50 @@ export class UpgradeNg1ComponentAdapterBuilder {
const context = (btcIsObject) ? this.directive !.bindToController : this.directive !.scope;
if (typeof context == 'object') {
for (const name in context) {
if ((<any>context).hasOwnProperty(name)) {
let localName = context[name];
const type = localName.charAt(0);
const typeOptions = localName.charAt(1);
localName = typeOptions === '?' ? localName.substr(2) : localName.substr(1);
localName = localName || name;
Object.keys(context).forEach(propName => {
const definition = context[propName];
const bindingType = definition.charAt(0);
const bindingOptions = definition.charAt(1);
const attrName = definition.substring(bindingOptions === '?' ? 2 : 1) || propName;
const outputName = 'output_' + name;
const outputNameRename = outputName + ': ' + name;
const outputNameRenameChange = outputName + ': ' + name + 'Change';
const inputName = 'input_' + name;
const inputNameRename = inputName + ': ' + name;
switch (type) {
case '=':
this.propertyOutputs.push(outputName);
this.checkProperties.push(localName);
this.outputs.push(outputName);
this.outputsRename.push(outputNameRenameChange);
this.propertyMap[outputName] = localName;
this.inputs.push(inputName);
this.inputsRename.push(inputNameRename);
this.propertyMap[inputName] = localName;
break;
case '@':
// handle the '<' binding of angular 1.5 components
case '<':
this.inputs.push(inputName);
this.inputsRename.push(inputNameRename);
this.propertyMap[inputName] = localName;
break;
case '&':
this.outputs.push(outputName);
this.outputsRename.push(outputNameRename);
this.propertyMap[outputName] = localName;
break;
default:
let json = JSON.stringify(context);
throw new Error(
`Unexpected mapping '${type}' in '${json}' in '${this.name}' directive.`);
}
// QUESTION: What about `=*`? Ignore? Throw? Support?
const inputName = `input_${attrName}`;
const inputNameRename = `${inputName}: ${attrName}`;
const outputName = `output_${attrName}`;
const outputNameRename = `${outputName}: ${attrName}`;
const outputNameRenameChange = `${outputNameRename}Change`;
switch (bindingType) {
case '@':
case '<':
this.inputs.push(inputName);
this.inputsRename.push(inputNameRename);
this.propertyMap[inputName] = propName;
break;
case '=':
this.inputs.push(inputName);
this.inputsRename.push(inputNameRename);
this.propertyMap[inputName] = propName;
this.outputs.push(outputName);
this.outputsRename.push(outputNameRenameChange);
this.propertyMap[outputName] = propName;
this.checkProperties.push(propName);
this.propertyOutputs.push(outputName);
break;
case '&':
this.outputs.push(outputName);
this.outputsRename.push(outputNameRename);
this.propertyMap[outputName] = propName;
break;
default:
let json = JSON.stringify(context);
throw new Error(
`Unexpected mapping '${bindingType}' in '${json}' in '${this.name}' directive.`);
}
}
});
}
}
@ -239,13 +240,11 @@ class UpgradeNg1ComponentAdapter implements OnInit, OnChanges, DoCheck {
(this as any /** TODO #9100 */)[inputs[i]] = null;
}
for (let j = 0; j < outputs.length; j++) {
const emitter = (this as any /** TODO #9100 */)[outputs[j]] = new EventEmitter();
const emitter = (this as any)[outputs[j]] = new EventEmitter<any>();
this.setComponentProperty(
outputs[j], ((emitter: any /** TODO #9100 */) => (value: any /** TODO #9100 */) =>
emitter.emit(value))(emitter));
outputs[j], (emitter => (value: any) => emitter.emit(value))(emitter));
}
for (let k = 0; k < propOuts.length; k++) {
(this as any /** TODO #9100 */)[propOuts[k]] = new EventEmitter();
this.checkLastValues.push(INITIAL_VALUE);
}
}
@ -306,18 +305,16 @@ class UpgradeNg1ComponentAdapter implements OnInit, OnChanges, DoCheck {
const destinationObj = this.destinationObj;
const lastValues = this.checkLastValues;
const checkProperties = this.checkProperties;
for (let i = 0; i < checkProperties.length; i++) {
const value = destinationObj ![checkProperties[i]];
const propOuts = this.propOuts;
checkProperties.forEach((propName, i) => {
const value = destinationObj ![propName];
const last = lastValues[i];
if (value !== last) {
if (typeof value == 'number' && isNaN(value) && typeof last == 'number' && isNaN(last)) {
// ignore because NaN != NaN
} else {
const eventEmitter: EventEmitter<any> = (this as any /** TODO #9100 */)[this.propOuts[i]];
eventEmitter.emit(lastValues[i] = value);
}
if (value !== last &&
(value === value || last === last)) { // ignore NaN values (NaN !== NaN)
const eventEmitter: EventEmitter<any> = (this as any)[propOuts[i]];
eventEmitter.emit(lastValues[i] = value);
}
}
});
if (this.controllerInstance && isFunction(this.controllerInstance.$doCheck)) {
this.controllerInstance.$doCheck();