Skip to content

Commit 2bc3986

Browse files
ocombeIgorMinar
authored andcommitted
feat(ivy): support inputs & outputs with aliases in component decorators (angular#27350)
PR Close angular#27350
1 parent 1279a50 commit 2bc3986

File tree

4 files changed

+90
-89
lines changed

4 files changed

+90
-89
lines changed

packages/compiler/src/render3/view/compiler.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ import {typeWithParameters} from '../util';
3030
import {R3ComponentDef, R3ComponentMetadata, R3DirectiveDef, R3DirectiveMetadata, R3QueryMetadata} from './api';
3131
import {StylingBuilder, StylingInstruction} from './styling';
3232
import {BindingScope, TemplateDefinitionBuilder, ValueConverter, renderFlagCheckIfStmt} from './template';
33-
import {CONTEXT_NAME, DefinitionMap, RENDER_FLAGS, TEMPORARY_NAME, asLiteral, conditionallyCreateMapObjectLiteral, getQueryPredicate, mapToExpression, temporaryAllocator} from './util';
33+
import {CONTEXT_NAME, DefinitionMap, RENDER_FLAGS, TEMPORARY_NAME, asLiteral, conditionallyCreateMapObjectLiteral, getQueryPredicate, temporaryAllocator} from './util';
3434

3535
const EMPTY_ARRAY: any[] = [];
3636

@@ -864,4 +864,4 @@ function parseNamedProperty(name: string): {propertyName: string, unit: string}
864864
}
865865
}
866866
return {propertyName, unit};
867-
}
867+
}

packages/compiler/src/render3/view/util.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88

99
import {ConstantPool} from '../../constant_pool';
1010
import * as o from '../../output/output_ast';
11+
import {splitAtColon} from '../../util';
1112
import * as t from '../r3_ast';
12-
1313
import {R3QueryMetadata} from './api';
1414
import {isI18nAttribute} from './i18n/util';
1515

@@ -75,9 +75,13 @@ export function conditionallyCreateMapObjectLiteral(keys: {[key: string]: string
7575
return null;
7676
}
7777

78-
export function mapToExpression(map: {[key: string]: any}, quoted = false): o.Expression {
79-
return o.literalMap(
80-
Object.getOwnPropertyNames(map).map(key => ({key, quoted, value: asLiteral(map[key])})));
78+
function mapToExpression(map: {[key: string]: any}): o.Expression {
79+
return o.literalMap(Object.getOwnPropertyNames(map).map(key => {
80+
// canonical syntax: `dirProp: elProp`
81+
// if there is no `:`, use dirProp = elProp
82+
const parts = splitAtColon(key, [key, map[key]]);
83+
return {key: parts[0], quoted: false, value: asLiteral(parts[1])};
84+
}));
8185
}
8286

8387
/**

packages/core/src/render3/instructions.ts

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -958,7 +958,7 @@ export function elementProperty<T>(
958958
if (isComponent(tNode)) markDirtyIfOnPush(lView, index + HEADER_OFFSET);
959959
if (ngDevMode) {
960960
if (tNode.type === TNodeType.Element || tNode.type === TNodeType.Container) {
961-
setNgReflectProperties(lView, element, tNode.type, propName, value);
961+
setNgReflectProperties(lView, element, tNode.type, dataValue, value);
962962
}
963963
}
964964
} else if (tNode.type === TNodeType.Element) {
@@ -1033,29 +1033,32 @@ function setInputsForProperty(lView: LView, inputs: PropertyAliasValue, value: a
10331033
}
10341034

10351035
function setNgReflectProperties(
1036-
lView: LView, element: RElement | RComment, type: TNodeType, propName: string, value: any) {
1037-
const renderer = lView[RENDERER];
1038-
const attrName = normalizeDebugBindingName(propName);
1039-
const debugValue = normalizeDebugBindingValue(value);
1040-
if (type === TNodeType.Element) {
1041-
isProceduralRenderer(renderer) ?
1042-
renderer.setAttribute((element as RElement), attrName, debugValue) :
1043-
(element as RElement).setAttribute(attrName, debugValue);
1044-
} else if (value !== undefined) {
1045-
const value = `bindings=${JSON.stringify({[attrName]: debugValue}, null, 2)}`;
1046-
if (isProceduralRenderer(renderer)) {
1047-
renderer.setValue((element as RComment), value);
1048-
} else {
1049-
(element as RComment).textContent = value;
1036+
lView: LView, element: RElement | RComment, type: TNodeType, inputs: PropertyAliasValue,
1037+
value: any) {
1038+
for (let i = 0; i < inputs.length; i += 2) {
1039+
const renderer = lView[RENDERER];
1040+
const attrName = normalizeDebugBindingName(inputs[i + 1] as string);
1041+
const debugValue = normalizeDebugBindingValue(value);
1042+
if (type === TNodeType.Element) {
1043+
isProceduralRenderer(renderer) ?
1044+
renderer.setAttribute((element as RElement), attrName, debugValue) :
1045+
(element as RElement).setAttribute(attrName, debugValue);
1046+
} else if (value !== undefined) {
1047+
const value = `bindings=${JSON.stringify({[attrName]: debugValue}, null, 2)}`;
1048+
if (isProceduralRenderer(renderer)) {
1049+
renderer.setValue((element as RComment), value);
1050+
} else {
1051+
(element as RComment).textContent = value;
1052+
}
10501053
}
10511054
}
10521055
}
10531056

10541057
/**
10551058
* Consolidates all inputs or outputs of all directives on this logical node.
10561059
*
1057-
* @param number tNodeFlags node flags
1058-
* @param Direction direction whether to consider inputs or outputs
1060+
* @param tNodeFlags node flags
1061+
* @param direction whether to consider inputs or outputs
10591062
* @returns PropertyAliases|null aggregate of all properties if any, `null` otherwise
10601063
*/
10611064
function generatePropertyAliases(tNode: TNode, direction: BindingDirection): PropertyAliases|null {

packages/core/test/linker/integration_spec.ts

Lines changed: 60 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -238,44 +238,42 @@ function declareTests(config?: {useJit: boolean}) {
238238
expect(getDOM().getProperty(nativeEl, 'htmlFor')).toBe('foo');
239239
});
240240

241-
fixmeIvy('FW-587: Inputs with aliases in component decorators don\'t work')
242-
.it('should consume directive watch expression change.', () => {
243-
TestBed.configureTestingModule({declarations: [MyComp, MyDir]});
244-
const template = '<span>' +
245-
'<div my-dir [elprop]="ctxProp"></div>' +
246-
'<div my-dir elprop="Hi there!"></div>' +
247-
'<div my-dir elprop="Hi {{\'there!\'}}"></div>' +
248-
'<div my-dir elprop="One more {{ctxProp}}"></div>' +
249-
'</span>';
250-
TestBed.overrideComponent(MyComp, {set: {template}});
251-
const fixture = TestBed.createComponent(MyComp);
241+
it('should consume directive watch expression change.', () => {
242+
TestBed.configureTestingModule({declarations: [MyComp, MyDir]});
243+
const template = '<span>' +
244+
'<div my-dir [elprop]="ctxProp"></div>' +
245+
'<div my-dir elprop="Hi there!"></div>' +
246+
'<div my-dir elprop="Hi {{\'there!\'}}"></div>' +
247+
'<div my-dir elprop="One more {{ctxProp}}"></div>' +
248+
'</span>';
249+
TestBed.overrideComponent(MyComp, {set: {template}});
250+
const fixture = TestBed.createComponent(MyComp);
252251

253-
fixture.componentInstance.ctxProp = 'Hello World!';
254-
fixture.detectChanges();
252+
fixture.componentInstance.ctxProp = 'Hello World!';
253+
fixture.detectChanges();
255254

256-
const containerSpan = fixture.debugElement.children[0];
255+
const containerSpan = fixture.debugElement.children[0];
257256

258-
expect(containerSpan.children[0].injector.get(MyDir).dirProp).toEqual('Hello World!');
259-
expect(containerSpan.children[1].injector.get(MyDir).dirProp).toEqual('Hi there!');
260-
expect(containerSpan.children[2].injector.get(MyDir).dirProp).toEqual('Hi there!');
261-
expect(containerSpan.children[3].injector.get(MyDir).dirProp)
262-
.toEqual('One more Hello World!');
263-
});
257+
expect(containerSpan.children[0].injector.get(MyDir).dirProp).toEqual('Hello World!');
258+
expect(containerSpan.children[1].injector.get(MyDir).dirProp).toEqual('Hi there!');
259+
expect(containerSpan.children[2].injector.get(MyDir).dirProp).toEqual('Hi there!');
260+
expect(containerSpan.children[3].injector.get(MyDir).dirProp)
261+
.toEqual('One more Hello World!');
262+
});
264263

265264
describe('pipes', () => {
266-
fixmeIvy('FW-587: Inputs with aliases in component decorators don\'t work')
267-
.it('should support pipes in bindings', () => {
268-
TestBed.configureTestingModule({declarations: [MyComp, MyDir, DoublePipe]});
269-
const template = '<div my-dir #dir="mydir" [elprop]="ctxProp | double"></div>';
270-
TestBed.overrideComponent(MyComp, {set: {template}});
271-
const fixture = TestBed.createComponent(MyComp);
265+
it('should support pipes in bindings', () => {
266+
TestBed.configureTestingModule({declarations: [MyComp, MyDir, DoublePipe]});
267+
const template = '<div my-dir #dir="mydir" [elprop]="ctxProp | double"></div>';
268+
TestBed.overrideComponent(MyComp, {set: {template}});
269+
const fixture = TestBed.createComponent(MyComp);
272270

273-
fixture.componentInstance.ctxProp = 'a';
274-
fixture.detectChanges();
271+
fixture.componentInstance.ctxProp = 'a';
272+
fixture.detectChanges();
275273

276-
const dir = fixture.debugElement.children[0].references !['dir'];
277-
expect(dir.dirProp).toEqual('aa');
278-
});
274+
const dir = fixture.debugElement.children[0].references !['dir'];
275+
expect(dir.dirProp).toEqual('aa');
276+
});
279277
});
280278

281279
it('should support nested components.', () => {
@@ -290,21 +288,20 @@ function declareTests(config?: {useJit: boolean}) {
290288
});
291289

292290
// GH issue 328 - https://github.com/angular/angular/issues/328
293-
fixmeIvy('FW-587: Inputs with aliases in component decorators don\'t work')
294-
.it('should support different directive types on a single node', () => {
295-
TestBed.configureTestingModule({declarations: [MyComp, ChildComp, MyDir]});
296-
const template = '<child-cmp my-dir [elprop]="ctxProp"></child-cmp>';
297-
TestBed.overrideComponent(MyComp, {set: {template}});
298-
const fixture = TestBed.createComponent(MyComp);
291+
it('should support different directive types on a single node', () => {
292+
TestBed.configureTestingModule({declarations: [MyComp, ChildComp, MyDir]});
293+
const template = '<child-cmp my-dir [elprop]="ctxProp"></child-cmp>';
294+
TestBed.overrideComponent(MyComp, {set: {template}});
295+
const fixture = TestBed.createComponent(MyComp);
299296

300-
fixture.componentInstance.ctxProp = 'Hello World!';
301-
fixture.detectChanges();
297+
fixture.componentInstance.ctxProp = 'Hello World!';
298+
fixture.detectChanges();
302299

303-
const tc = fixture.debugElement.children[0];
300+
const tc = fixture.debugElement.children[0];
304301

305-
expect(tc.injector.get(MyDir).dirProp).toEqual('Hello World!');
306-
expect(tc.injector.get(ChildComp).dirProp).toEqual(null);
307-
});
302+
expect(tc.injector.get(MyDir).dirProp).toEqual('Hello World!');
303+
expect(tc.injector.get(ChildComp).dirProp).toEqual(null);
304+
});
308305

309306
it('should support directives where a binding attribute is not given', () => {
310307
TestBed.configureTestingModule({declarations: [MyComp, MyDir]});
@@ -1688,21 +1685,20 @@ function declareTests(config?: {useJit: boolean}) {
16881685
});
16891686

16901687
describe('logging property updates', () => {
1691-
fixmeIvy('FW-587: Inputs with aliases in component decorators don\'t work')
1692-
.it('should reflect property values as attributes', () => {
1693-
TestBed.configureTestingModule({declarations: [MyComp, MyDir]});
1694-
const template = '<div>' +
1695-
'<div my-dir [elprop]="ctxProp"></div>' +
1696-
'</div>';
1697-
TestBed.overrideComponent(MyComp, {set: {template}});
1698-
const fixture = TestBed.createComponent(MyComp);
1688+
it('should reflect property values as attributes', () => {
1689+
TestBed.configureTestingModule({declarations: [MyComp, MyDir]});
1690+
const template = '<div>' +
1691+
'<div my-dir [elprop]="ctxProp"></div>' +
1692+
'</div>';
1693+
TestBed.overrideComponent(MyComp, {set: {template}});
1694+
const fixture = TestBed.createComponent(MyComp);
16991695

1700-
fixture.componentInstance.ctxProp = 'hello';
1701-
fixture.detectChanges();
1696+
fixture.componentInstance.ctxProp = 'hello';
1697+
fixture.detectChanges();
17021698

1703-
expect(getDOM().getInnerHTML(fixture.nativeElement))
1704-
.toContain('ng-reflect-dir-prop="hello"');
1705-
});
1699+
expect(getDOM().getInnerHTML(fixture.nativeElement))
1700+
.toContain('ng-reflect-dir-prop="hello"');
1701+
});
17061702

17071703
it(`should work with prop names containing '$'`, () => {
17081704
TestBed.configureTestingModule({declarations: [ParentCmp, SomeCmpWithInput]});
@@ -1726,17 +1722,15 @@ function declareTests(config?: {useJit: boolean}) {
17261722
.toContain('"ng\-reflect\-ng\-if"\: "true"');
17271723
});
17281724

1729-
// also affected by FW-587: Inputs with aliases in component decorators don't work
1730-
fixmeIvy('FW-664: ng-reflect-* is not supported')
1731-
.it('should indicate when toString() throws', () => {
1732-
TestBed.configureTestingModule({declarations: [MyComp, MyDir]});
1733-
const template = '<div my-dir [elprop]="toStringThrow"></div>';
1734-
TestBed.overrideComponent(MyComp, {set: {template}});
1735-
const fixture = TestBed.createComponent(MyComp);
1725+
it('should indicate when toString() throws', () => {
1726+
TestBed.configureTestingModule({declarations: [MyComp, MyDir]});
1727+
const template = '<div my-dir [elprop]="toStringThrow"></div>';
1728+
TestBed.overrideComponent(MyComp, {set: {template}});
1729+
const fixture = TestBed.createComponent(MyComp);
17361730

1737-
fixture.detectChanges();
1738-
expect(getDOM().getInnerHTML(fixture.nativeElement)).toContain('[ERROR]');
1739-
});
1731+
fixture.detectChanges();
1732+
expect(getDOM().getInnerHTML(fixture.nativeElement)).toContain('[ERROR]');
1733+
});
17401734
});
17411735

17421736
describe('property decorators', () => {

0 commit comments

Comments
 (0)