From 5266ffe04a5f00d896fae76b68707a2e4035dad8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mi=C5=A1ko=20Hevery?= Date: Fri, 23 Mar 2018 10:55:17 -0700 Subject: [PATCH] feat(ivy): add `(event)="handle"` syntax support to compiler (#22921) PR Close #22921 --- packages/compiler/src/output/output_ast.ts | 4 ++ .../compiler/src/render3/r3_identifiers.ts | 3 +- .../compiler/src/render3/r3_view_compiler.ts | 70 ++++++++----------- .../render3/r3_compiler_compliance_spec.ts | 29 -------- .../render3/r3_view_compiler_listener_spec.ts | 62 ++++++++++++++++ 5 files changed, 96 insertions(+), 72 deletions(-) create mode 100644 packages/compiler/test/render3/r3_view_compiler_listener_spec.ts diff --git a/packages/compiler/src/output/output_ast.ts b/packages/compiler/src/output/output_ast.ts index 9234c64b11..246adf27c8 100644 --- a/packages/compiler/src/output/output_ast.ts +++ b/packages/compiler/src/output/output_ast.ts @@ -1485,6 +1485,10 @@ export function literal( 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. export const enum JSDocTagName { Desc = 'desc', diff --git a/packages/compiler/src/render3/r3_identifiers.ts b/packages/compiler/src/render3/r3_identifiers.ts index 2bddfd9523..c9b3ab8a26 100644 --- a/packages/compiler/src/render3/r3_identifiers.ts +++ b/packages/compiler/src/render3/r3_identifiers.ts @@ -12,8 +12,7 @@ const CORE = '@angular/core'; export class Identifiers { /* Methods */ - static NEW_METHOD = 'n'; - static HOST_BINDING_METHOD = 'h'; + static NEW_METHOD = 'factory'; static TRANSFORM_METHOD = 'transform'; static PATCH_DEPS = 'patchedDeps'; diff --git a/packages/compiler/src/render3/r3_view_compiler.ts b/packages/compiler/src/render3/r3_view_compiler.ts index cf9f6d5fad..e03c6384c4 100644 --- a/packages/compiler/src/render3/r3_view_compiler.ts +++ b/packages/compiler/src/render3/r3_view_compiler.ts @@ -353,7 +353,6 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver { private _prefix: o.Statement[] = []; private _creationMode: o.Statement[] = []; private _bindingMode: o.Statement[] = []; - private _hostMode: o.Statement[] = []; private _refreshMode: o.Statement[] = []; private _postfix: o.Statement[] = []; private _contentProjections: Map; @@ -482,8 +481,6 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver { ...creationMode, // Binding mode (i.e. ɵp(...)) ...this._bindingMode, - // Host mode (i.e. Comp.h(...)) - ...this._hostMode, // Refresh mode (i.e. Comp.r(...)) ...this._refreshMode, // Nested templates (i.e. function CompTemplate() {}) @@ -508,19 +505,16 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver { } private _computeDirectivesArray(directives: DirectiveAst[]) { - const directiveIndexMap = new Map(); const directiveExpressions: o.Expression[] = 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 { - directivesArray: directiveExpressions.length ? - this.constantPool.getConstLiteral( - o.literalArr(directiveExpressions), /* forceShared */ true) : - o.literal(null), - directiveIndexMap - }; + return directiveExpressions.length ? + this.constantPool.getConstLiteral( + o.literalArr(directiveExpressions), /* forceShared */ true) : + o.literal(null, o.INFERRED_TYPE); + ; } // TemplateAstVisitor @@ -605,13 +599,8 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver { parameters.push(attrArg); // Add directives array - const {directivesArray, directiveIndexMap} = this._computeDirectivesArray(element.directives); - parameters.push(directiveIndexMap.size > 0 ? directivesArray : nullNode); - - if (component && componentIndex != null) { - // Record the data slot for the component - directiveIndexMap.set(component.directive.type.reference, componentIndex); - } + const directivesArray = this._computeDirectivesArray(element.directives); + parameters.push(directivesArray); if (element.references && element.references.length > 0) { const references = @@ -633,7 +622,7 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver { } // Remove trailing null nodes as they are implied. - while (parameters[parameters.length - 1] === nullNode) { + while (o.isNull(parameters[parameters.length - 1])) { parameters.pop(); } @@ -645,6 +634,21 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver { 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 for (let input of element.inputs) { if (input.isAnimation) { @@ -663,7 +667,7 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver { } // Generate directives input bindings - this._visitDirectives(element.directives, implicit, elementIndex, directiveIndexMap); + this._visitDirectives(element.directives, implicit, elementIndex); // Traverse element child nodes if (this._inI18nSection && element.children.length == 1 && @@ -682,12 +686,8 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver { this._inI18nSection = wasInI18nSection; } - private _visitDirectives( - directives: DirectiveAst[], implicit: o.Expression, nodeIndex: number, - directiveIndexMap: Map) { + private _visitDirectives(directives: DirectiveAst[], implicit: o.Expression, nodeIndex: number) { for (let directive of directives) { - const directiveIndex = directiveIndexMap.get(directive.directive.type.reference); - // Creation mode // e.g. D(0, TodoComponentDef.n(), TodoComponentDef); const directiveType = directive.directive.type.reference; @@ -705,17 +705,6 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver { this._bindingMode, directive.sourceSpan, R3.elementProperty, o.literal(nodeIndex), 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}`; const templateContext = `ctx${this.level}`; - const {directivesArray, directiveIndexMap} = this._computeDirectivesArray(ast.directives); + const directivesArray = this._computeDirectivesArray(ast.directives); // e.g. C(1, C1Template) this.instruction( @@ -748,8 +737,7 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver { this._refreshMode, ast.sourceSpan, R3.containerRefreshStart, o.literal(templateIndex)); // Generate directives - this._visitDirectives( - ast.directives, o.variable(this.contextParameter), templateIndex, directiveIndexMap); + this._visitDirectives(ast.directives, o.variable(this.contextParameter), templateIndex); // e.g. cr(); this.instruction(this._refreshMode, ast.sourceSpan, R3.containerRefreshEnd); @@ -1027,7 +1015,7 @@ function createHostBindingsFunction( const functionName = typeName && bindingName ? `${typeName}_${bindingName}_HostBindingHandler` : null; 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, null, functionName); statements.push( diff --git a/packages/compiler/test/render3/r3_compiler_compliance_spec.ts b/packages/compiler/test/render3/r3_compiler_compliance_spec.ts index 25a7cfd127..fe344a0c15 100644 --- a/packages/compiler/test/render3/r3_compiler_compliance_spec.ts +++ b/packages/compiler/test/render3/r3_compiler_compliance_spec.ts @@ -125,10 +125,6 @@ describe('compiler compliance', () => { $r3$.ɵe(); $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(); } const $foo$ = $r3$.ɵld(1); - IfDirective.ngDirectiveDef.h(3,2); $r3$.ɵcR(2); - $r3$.ɵr(3,2); $r3$.ɵcr(); function MyComponent_IfDirective_Template_2(ctx0: IDENT, cm: IDENT) { @@ -337,8 +331,6 @@ describe('compiler compliance', () => { $r3$.ɵe(); } $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( 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))); - MyComp.ngComponentDef.h(1, 0); - $r3$.ɵr(1, 0); } }); `; @@ -475,8 +465,6 @@ describe('compiler compliance', () => { $r3$.ɵe(); } $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', $r3$.ɵb($r3$.ɵf2( $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$.ɵ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 $c1$ = LifecycleComp.ngComponentDef; - … static ngComponentDef = $r3$.ɵdefineComponent({ type: SimpleLayout, selectors: [['simple-layout']], @@ -954,10 +936,6 @@ describe('compiler compliance', () => { } $r3$.ɵp(0, 'name', $r3$.ɵb(ctx.name1)); $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$.ɵp(1, 'forOf', $r3$.ɵb(ctx.items)); - ForOfDirective.ngDirectiveDef.h(2, 1); $r3$.ɵcR(1); - $r3$.ɵr(2, 1); $r3$.ɵcr(); function MyComponent_ForOfDirective_Template_1(ctx0: IDENT, cm: IDENT) { @@ -1139,7 +1115,6 @@ describe('compiler compliance', () => { const MyComponentDefinition = ` const $c1$ = [ForOfDirective]; - const $c2$ = ForOfDirective.ngDirectiveDef; … static ngComponentDef = $r3$.ɵdefineComponent({ type: MyComponent, @@ -1152,9 +1127,7 @@ describe('compiler compliance', () => { $r3$.ɵe(); } $r3$.ɵp(1, 'forOf', $r3$.ɵb(ctx.items)); - $c2$.h(2,1); $r3$.ɵcR(1); - $r3$.ɵr(2, 1); $r3$.ɵcr(); function MyComponent_ForOfDirective_Template_1(ctx0: IDENT, cm: IDENT) { @@ -1170,10 +1143,8 @@ describe('compiler compliance', () => { } const $item$ = ctx0.$implicit; $r3$.ɵp(4, 'forOf', $r3$.ɵb(IDENT.infos)); - $c2$.h(5,4); $r3$.ɵt(2, $r3$.ɵi1('', IDENT.name, '')); $r3$.ɵcR(4); - $r3$.ɵr(5, 4); $r3$.ɵcr(); function MyComponent_ForOfDirective_ForOfDirective_Template_4( diff --git a/packages/compiler/test/render3/r3_view_compiler_listener_spec.ts b/packages/compiler/test/render3/r3_view_compiler_listener_spec.ts new file mode 100644 index 0000000000..efe8a8b13e --- /dev/null +++ b/packages/compiler/test/render3/r3_view_compiler_listener_spec.ts @@ -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: \`
\` + }) + 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'); + }); + +}); \ No newline at end of file