feat(ivy): add `(event)="handle"` syntax support to compiler (#22921)

PR Close #22921
This commit is contained in:
Miško Hevery 2018-03-23 10:55:17 -07:00 committed by Alex Rickabaugh
parent 4290ea4bb9
commit 5266ffe04a
5 changed files with 96 additions and 72 deletions

View File

@ -1485,6 +1485,10 @@ export function literal(
return new LiteralExpr(value, type, sourceSpan); return new LiteralExpr(value, type, sourceSpan);
} }
export function isNull(exp: Expression): boolean {
return exp instanceof LiteralExpr && exp.value === null;
}
// The list of JSDoc tags that we currently support. Extend it if needed. // The list of JSDoc tags that we currently support. Extend it if needed.
export const enum JSDocTagName { export const enum JSDocTagName {
Desc = 'desc', Desc = 'desc',

View File

@ -12,8 +12,7 @@ const CORE = '@angular/core';
export class Identifiers { export class Identifiers {
/* Methods */ /* Methods */
static NEW_METHOD = 'n'; static NEW_METHOD = 'factory';
static HOST_BINDING_METHOD = 'h';
static TRANSFORM_METHOD = 'transform'; static TRANSFORM_METHOD = 'transform';
static PATCH_DEPS = 'patchedDeps'; static PATCH_DEPS = 'patchedDeps';

View File

@ -353,7 +353,6 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
private _prefix: o.Statement[] = []; private _prefix: o.Statement[] = [];
private _creationMode: o.Statement[] = []; private _creationMode: o.Statement[] = [];
private _bindingMode: o.Statement[] = []; private _bindingMode: o.Statement[] = [];
private _hostMode: o.Statement[] = [];
private _refreshMode: o.Statement[] = []; private _refreshMode: o.Statement[] = [];
private _postfix: o.Statement[] = []; private _postfix: o.Statement[] = [];
private _contentProjections: Map<NgContentAst, NgContentInfo>; private _contentProjections: Map<NgContentAst, NgContentInfo>;
@ -482,8 +481,6 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
...creationMode, ...creationMode,
// Binding mode (i.e. ɵp(...)) // Binding mode (i.e. ɵp(...))
...this._bindingMode, ...this._bindingMode,
// Host mode (i.e. Comp.h(...))
...this._hostMode,
// Refresh mode (i.e. Comp.r(...)) // Refresh mode (i.e. Comp.r(...))
...this._refreshMode, ...this._refreshMode,
// Nested templates (i.e. function CompTemplate() {}) // Nested templates (i.e. function CompTemplate() {})
@ -508,19 +505,16 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
} }
private _computeDirectivesArray(directives: DirectiveAst[]) { private _computeDirectivesArray(directives: DirectiveAst[]) {
const directiveIndexMap = new Map<any, number>();
const directiveExpressions: o.Expression[] = const directiveExpressions: o.Expression[] =
directives.filter(directive => !directive.directive.isComponent).map(directive => { directives.filter(directive => !directive.directive.isComponent).map(directive => {
directiveIndexMap.set(directive.directive.type.reference, this.allocateDataSlot()); this.allocateDataSlot(); // Allocate space for the directive
return this.typeReference(directive.directive.type.reference); return this.typeReference(directive.directive.type.reference);
}); });
return { return directiveExpressions.length ?
directivesArray: directiveExpressions.length ? this.constantPool.getConstLiteral(
this.constantPool.getConstLiteral( o.literalArr(directiveExpressions), /* forceShared */ true) :
o.literalArr(directiveExpressions), /* forceShared */ true) : o.literal(null, o.INFERRED_TYPE);
o.literal(null), ;
directiveIndexMap
};
} }
// TemplateAstVisitor // TemplateAstVisitor
@ -605,13 +599,8 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
parameters.push(attrArg); parameters.push(attrArg);
// Add directives array // Add directives array
const {directivesArray, directiveIndexMap} = this._computeDirectivesArray(element.directives); const directivesArray = this._computeDirectivesArray(element.directives);
parameters.push(directiveIndexMap.size > 0 ? directivesArray : nullNode); parameters.push(directivesArray);
if (component && componentIndex != null) {
// Record the data slot for the component
directiveIndexMap.set(component.directive.type.reference, componentIndex);
}
if (element.references && element.references.length > 0) { if (element.references && element.references.length > 0) {
const references = const references =
@ -633,7 +622,7 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
} }
// Remove trailing null nodes as they are implied. // Remove trailing null nodes as they are implied.
while (parameters[parameters.length - 1] === nullNode) { while (o.isNull(parameters[parameters.length - 1])) {
parameters.pop(); parameters.pop();
} }
@ -645,6 +634,21 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
const implicit = o.variable(this.contextParameter); const implicit = o.variable(this.contextParameter);
// Generate Listeners (outputs)
element.outputs.forEach((outputAst: BoundEventAst) => {
const functionName = `${this.templateName}_${element.name}_${outputAst.name}_listener`;
const bindingExpr = convertActionBinding(
this, implicit, outputAst.handler, 'b', () => error('Unexpected interpolation'));
const handler = o.fn(
[new o.FnParam('$event', o.DYNAMIC_TYPE)],
[...bindingExpr.stmts, new o.ReturnStatement(bindingExpr.allowDefault)], o.INFERRED_TYPE,
null, functionName);
this.instruction(
this._creationMode, outputAst.sourceSpan, R3.listener, o.literal(outputAst.name),
handler);
});
// Generate element input bindings // Generate element input bindings
for (let input of element.inputs) { for (let input of element.inputs) {
if (input.isAnimation) { if (input.isAnimation) {
@ -663,7 +667,7 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
} }
// Generate directives input bindings // Generate directives input bindings
this._visitDirectives(element.directives, implicit, elementIndex, directiveIndexMap); this._visitDirectives(element.directives, implicit, elementIndex);
// Traverse element child nodes // Traverse element child nodes
if (this._inI18nSection && element.children.length == 1 && if (this._inI18nSection && element.children.length == 1 &&
@ -682,12 +686,8 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
this._inI18nSection = wasInI18nSection; this._inI18nSection = wasInI18nSection;
} }
private _visitDirectives( private _visitDirectives(directives: DirectiveAst[], implicit: o.Expression, nodeIndex: number) {
directives: DirectiveAst[], implicit: o.Expression, nodeIndex: number,
directiveIndexMap: Map<any, number>) {
for (let directive of directives) { for (let directive of directives) {
const directiveIndex = directiveIndexMap.get(directive.directive.type.reference);
// Creation mode // Creation mode
// e.g. D(0, TodoComponentDef.n(), TodoComponentDef); // e.g. D(0, TodoComponentDef.n(), TodoComponentDef);
const directiveType = directive.directive.type.reference; const directiveType = directive.directive.type.reference;
@ -705,17 +705,6 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
this._bindingMode, directive.sourceSpan, R3.elementProperty, o.literal(nodeIndex), this._bindingMode, directive.sourceSpan, R3.elementProperty, o.literal(nodeIndex),
o.literal(input.templateName), o.importExpr(R3.bind).callFn([convertedBinding])); o.literal(input.templateName), o.importExpr(R3.bind).callFn([convertedBinding]));
} }
// e.g. MyDirective.ngDirectiveDef.h(0, 0);
this._hostMode.push(
this.definitionOf(directiveType, kind)
.callMethod(R3.HOST_BINDING_METHOD, [o.literal(directiveIndex), o.literal(nodeIndex)])
.toStmt());
// e.g. r(0, 0);
this.instruction(
this._refreshMode, directive.sourceSpan, R3.refreshComponent, o.literal(directiveIndex),
o.literal(nodeIndex));
} }
} }
@ -736,7 +725,7 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
contextName ? `${contextName}_Template_${templateIndex}` : `Template_${templateIndex}`; contextName ? `${contextName}_Template_${templateIndex}` : `Template_${templateIndex}`;
const templateContext = `ctx${this.level}`; const templateContext = `ctx${this.level}`;
const {directivesArray, directiveIndexMap} = this._computeDirectivesArray(ast.directives); const directivesArray = this._computeDirectivesArray(ast.directives);
// e.g. C(1, C1Template) // e.g. C(1, C1Template)
this.instruction( this.instruction(
@ -748,8 +737,7 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
this._refreshMode, ast.sourceSpan, R3.containerRefreshStart, o.literal(templateIndex)); this._refreshMode, ast.sourceSpan, R3.containerRefreshStart, o.literal(templateIndex));
// Generate directives // Generate directives
this._visitDirectives( this._visitDirectives(ast.directives, o.variable(this.contextParameter), templateIndex);
ast.directives, o.variable(this.contextParameter), templateIndex, directiveIndexMap);
// e.g. cr(); // e.g. cr();
this.instruction(this._refreshMode, ast.sourceSpan, R3.containerRefreshEnd); this.instruction(this._refreshMode, ast.sourceSpan, R3.containerRefreshEnd);
@ -1027,7 +1015,7 @@ function createHostBindingsFunction(
const functionName = const functionName =
typeName && bindingName ? `${typeName}_${bindingName}_HostBindingHandler` : null; typeName && bindingName ? `${typeName}_${bindingName}_HostBindingHandler` : null;
const handler = o.fn( const handler = o.fn(
[new o.FnParam('event', o.DYNAMIC_TYPE)], [new o.FnParam('$event', o.DYNAMIC_TYPE)],
[...bindingExpr.stmts, new o.ReturnStatement(bindingExpr.allowDefault)], o.INFERRED_TYPE, [...bindingExpr.stmts, new o.ReturnStatement(bindingExpr.allowDefault)], o.INFERRED_TYPE,
null, functionName); null, functionName);
statements.push( statements.push(

View File

@ -125,10 +125,6 @@ describe('compiler compliance', () => {
$r3$.ɵe(); $r3$.ɵe();
$r3$.ɵT(3, '!'); $r3$.ɵT(3, '!');
} }
ChildComponent.ngComponentDef.h(1, 0);
SomeDirective.ngDirectiveDef.h(2, 0);
$r3$.ɵr(1, 0);
$r3$.ɵr(2, 0);
} }
}); });
`; `;
@ -266,9 +262,7 @@ describe('compiler compliance', () => {
$r3$.ɵe(); $r3$.ɵe();
} }
const $foo$ = $r3$.ɵld(1); const $foo$ = $r3$.ɵld(1);
IfDirective.ngDirectiveDef.h(3,2);
$r3$.ɵcR(2); $r3$.ɵcR(2);
$r3$.ɵr(3,2);
$r3$.ɵcr(); $r3$.ɵcr();
function MyComponent_IfDirective_Template_2(ctx0: IDENT, cm: IDENT) { function MyComponent_IfDirective_Template_2(ctx0: IDENT, cm: IDENT) {
@ -337,8 +331,6 @@ describe('compiler compliance', () => {
$r3$.ɵe(); $r3$.ɵe();
} }
$r3$.ɵp(0, 'names', $r3$.ɵb($r3$.ɵf1($e0_ff$, ctx.customName))); $r3$.ɵp(0, 'names', $r3$.ɵb($r3$.ɵf1($e0_ff$, ctx.customName)));
MyComp.ngComponentDef.h(1, 0);
$r3$.ɵr(1, 0);
} }
}); });
`; `;
@ -417,8 +409,6 @@ describe('compiler compliance', () => {
$r3$.ɵp( $r3$.ɵp(
0, 'names', 0, 'names',
$r3$.ɵb($r3$.ɵfV($e0_ff$, ctx.n0, ctx.n1, ctx.n2, ctx.n3, ctx.n4, ctx.n5, ctx.n6, ctx.n7, ctx.n8))); $r3$.ɵb($r3$.ɵfV($e0_ff$, ctx.n0, ctx.n1, ctx.n2, ctx.n3, ctx.n4, ctx.n5, ctx.n6, ctx.n7, ctx.n8)));
MyComp.ngComponentDef.h(1, 0);
$r3$.ɵr(1, 0);
} }
}); });
`; `;
@ -475,8 +465,6 @@ describe('compiler compliance', () => {
$r3$.ɵe(); $r3$.ɵe();
} }
$r3$.ɵp(0, 'config', $r3$.ɵb($r3$.ɵf1($e0_ff$, ctx.name))); $r3$.ɵp(0, 'config', $r3$.ɵb($r3$.ɵf1($e0_ff$, ctx.name)));
ObjectComp.ngComponentDef.h(1, 0);
$r3$.ɵr(1, 0);
} }
}); });
`; `;
@ -542,8 +530,6 @@ describe('compiler compliance', () => {
0, 'config', 0, 'config',
$r3$.ɵb($r3$.ɵf2( $r3$.ɵb($r3$.ɵf2(
$e0_ff_2$, ctx.name, $r3$.ɵf1($e0_ff_1$, $r3$.ɵf1($e0_ff$, ctx.duration))))); $e0_ff_2$, ctx.name, $r3$.ɵf1($e0_ff_1$, $r3$.ɵf1($e0_ff$, ctx.duration)))));
NestedComp.ngComponentDef.h(1, 0);
$r3$.ɵr(1, 0);
} }
}); });
`; `;
@ -683,8 +669,6 @@ describe('compiler compliance', () => {
$r3$.ɵe(); $r3$.ɵe();
} }
($r3$.ɵqR(($tmp$ = $r3$.ɵld(0))) && (ctx.someDir = $tmp$.first)); ($r3$.ɵqR(($tmp$ = $r3$.ɵld(0))) && (ctx.someDir = $tmp$.first));
SomeDirective.ngDirectiveDef.h(2, 1);
$r3$.ɵr(2, 1);
} }
});`; });`;
@ -939,8 +923,6 @@ describe('compiler compliance', () => {
});`; });`;
const SimpleLayoutDefinition = ` const SimpleLayoutDefinition = `
const $c1$ = LifecycleComp.ngComponentDef;
static ngComponentDef = $r3$.ɵdefineComponent({ static ngComponentDef = $r3$.ɵdefineComponent({
type: SimpleLayout, type: SimpleLayout,
selectors: [['simple-layout']], selectors: [['simple-layout']],
@ -954,10 +936,6 @@ describe('compiler compliance', () => {
} }
$r3$.ɵp(0, 'name', $r3$.ɵb(ctx.name1)); $r3$.ɵp(0, 'name', $r3$.ɵb(ctx.name1));
$r3$.ɵp(2, 'name', $r3$.ɵb(ctx.name2)); $r3$.ɵp(2, 'name', $r3$.ɵb(ctx.name2));
$c1$.h(1, 0);
$c1$.h(3, 2);
$r3$.ɵr(1, 0);
$r3$.ɵr(3, 2);
} }
});`; });`;
@ -1074,9 +1052,7 @@ describe('compiler compliance', () => {
$r3$.ɵe(); $r3$.ɵe();
} }
$r3$.ɵp(1, 'forOf', $r3$.ɵb(ctx.items)); $r3$.ɵp(1, 'forOf', $r3$.ɵb(ctx.items));
ForOfDirective.ngDirectiveDef.h(2, 1);
$r3$.ɵcR(1); $r3$.ɵcR(1);
$r3$.ɵr(2, 1);
$r3$.ɵcr(); $r3$.ɵcr();
function MyComponent_ForOfDirective_Template_1(ctx0: IDENT, cm: IDENT) { function MyComponent_ForOfDirective_Template_1(ctx0: IDENT, cm: IDENT) {
@ -1139,7 +1115,6 @@ describe('compiler compliance', () => {
const MyComponentDefinition = ` const MyComponentDefinition = `
const $c1$ = [ForOfDirective]; const $c1$ = [ForOfDirective];
const $c2$ = ForOfDirective.ngDirectiveDef;
static ngComponentDef = $r3$.ɵdefineComponent({ static ngComponentDef = $r3$.ɵdefineComponent({
type: MyComponent, type: MyComponent,
@ -1152,9 +1127,7 @@ describe('compiler compliance', () => {
$r3$.ɵe(); $r3$.ɵe();
} }
$r3$.ɵp(1, 'forOf', $r3$.ɵb(ctx.items)); $r3$.ɵp(1, 'forOf', $r3$.ɵb(ctx.items));
$c2$.h(2,1);
$r3$.ɵcR(1); $r3$.ɵcR(1);
$r3$.ɵr(2, 1);
$r3$.ɵcr(); $r3$.ɵcr();
function MyComponent_ForOfDirective_Template_1(ctx0: IDENT, cm: IDENT) { function MyComponent_ForOfDirective_Template_1(ctx0: IDENT, cm: IDENT) {
@ -1170,10 +1143,8 @@ describe('compiler compliance', () => {
} }
const $item$ = ctx0.$implicit; const $item$ = ctx0.$implicit;
$r3$.ɵp(4, 'forOf', $r3$.ɵb(IDENT.infos)); $r3$.ɵp(4, 'forOf', $r3$.ɵb(IDENT.infos));
$c2$.h(5,4);
$r3$.ɵt(2, $r3$.ɵi1('', IDENT.name, '')); $r3$.ɵt(2, $r3$.ɵi1('', IDENT.name, ''));
$r3$.ɵcR(4); $r3$.ɵcR(4);
$r3$.ɵr(5, 4);
$r3$.ɵcr(); $r3$.ɵcr();
function MyComponent_ForOfDirective_ForOfDirective_Template_4( function MyComponent_ForOfDirective_ForOfDirective_Template_4(

View File

@ -0,0 +1,62 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {MockDirectory, setup} from '../aot/test_util';
import {compile, expectEmit} from './mock_compile';
/* These tests are codified version of the tests in compiler_canonical_spec.ts. Every
* test in compiler_canonical_spec.ts should have a corresponding test here.
*/
describe('compiler compliance: listen()', () => {
const angularFiles = setup({
compileAngular: true,
compileAnimations: false,
compileCommon: true,
});
it('should create listener instruction on element', () => {
const files = {
app: {
'spec.ts': `
import {Component, NgModule} from '@angular/core';
@Component({
selector: 'my-component',
template: \`<div (click)="onClick($event)"></div>\`
})
export class MyComponent {
onClick(event: any) {}
}
@NgModule({declarations: [MyComponent]})
export class MyModule {}
`
}
};
// The template should look like this (where IDENT is a wild card for an identifier):
const template = `
template: function MyComponent_Template(ctx: $MyComponent$, cm: $boolean$) {
if (cm) {
$r3$.ɵE(0, 'div');
$r3$.ɵL('click', function MyComponent_Template_div_click_listener($event: $any$) {
const $return_value$:$any$ = ctx.onClick($event) ;
return $return_value$;
});
$r3$.ɵe();
}
}
`;
const result = compile(files, angularFiles);
expectEmit(result.source, template, 'Incorrect template');
});
});