From 0042cac7be2f162616babff4e0f96b4246987823 Mon Sep 17 00:00:00 2001 From: Pawel Kozlowski Date: Wed, 29 Aug 2018 12:26:24 +0200 Subject: [PATCH] fix(ivy): support directive outputs on ng-template Compiler part of #25698 Fixes #25697 --- .../r3_view_compiler_template_spec.ts | 39 +++++++++++++ .../compiler/src/render3/view/template.ts | 57 ++++++++++++------- packages/core/src/render3/STATUS.md | 2 +- 3 files changed, 75 insertions(+), 23 deletions(-) diff --git a/packages/compiler-cli/test/compliance/r3_view_compiler_template_spec.ts b/packages/compiler-cli/test/compliance/r3_view_compiler_template_spec.ts index 6e7b5e795cb89..1e3dce804f30b 100644 --- a/packages/compiler-cli/test/compliance/r3_view_compiler_template_spec.ts +++ b/packages/compiler-cli/test/compliance/r3_view_compiler_template_spec.ts @@ -401,4 +401,43 @@ describe('compiler compliance: template', () => { expectEmit(result.source, template, 'Incorrect template'); }); + + it('should support directive outputs on ', () => { + + const files = { + app: { + 'spec.ts': ` + import {Component, NgModule} from '@angular/core'; + + @Component({ + selector: 'my-component', + template: ''; + }) + export class MyComponent {} + + @NgModule({declarations: [MyComponent]}) + export class MyModule {} + ` + } + }; + + const template = ` + const $t0_attrs$ = [${AttributeMarker.SelectOnly}, "outDirective"]; + + function Template_0(rf, ctx) { } + + // ... + + template: function MyComponent_Template(rf, ctx) { + if (rf & 1) { + $i0$.ɵtemplate(0, Template_0, 0, 0, null, $t0_attrs$); + $i0$.ɵlistener("outDirective", function MyComponent_Template_ng_template_outDirective_listener($event) { return $event.doSth(); }); + } + }`; + + const result = compile(files, angularFiles); + + expectEmit(result.source, template, 'Incorrect template'); + + }); }); diff --git a/packages/compiler/src/render3/view/template.ts b/packages/compiler/src/render3/view/template.ts index 09827c1cbc1a3..421464e82aebf 100644 --- a/packages/compiler/src/render3/view/template.ts +++ b/packages/compiler/src/render3/view/template.ts @@ -516,28 +516,9 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver // Generate Listeners (outputs) element.outputs.forEach((outputAst: t.BoundEvent) => { - const elName = sanitizeIdentifier(element.name); - const evName = sanitizeIdentifier(outputAst.name); - const functionName = `${this.templateName}_${elName}_${evName}_listener`; - - this.creationInstruction(outputAst.sourceSpan, R3.listener, () => { - const listenerScope = this._bindingScope.nestedScope(this._bindingScope.bindingLevel); - - const bindingExpr = convertActionBinding( - listenerScope, implicit, outputAst.handler, 'b', - () => error('Unexpected interpolation')); - - const statements = [ - ...listenerScope.restoreViewStatement(), ...listenerScope.variableDeclarations(), - ...bindingExpr.render3Stmts - ]; - - const handler = o.fn( - [new o.FnParam('$event', o.DYNAMIC_TYPE)], statements, o.INFERRED_TYPE, null, - functionName); - - return [o.literal(outputAst.name), handler]; - }); + this.creationInstruction( + outputAst.sourceSpan, R3.listener, + this.prepareListenerParameter(element.name, outputAst)); }); } @@ -736,6 +717,13 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver o.literal(templateVisitor.getVarCount())); return trimTrailingNulls(parameters); }); + + // Generate listeners for directive output + template.outputs.forEach((outputAst: t.BoundEvent) => { + this.creationInstruction( + outputAst.sourceSpan, R3.listener, + this.prepareListenerParameter('ng_template', outputAst)); + }); } // These should be handled in the template or element directly. @@ -906,6 +894,31 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver return this.constantPool.getConstLiteral(asLiteral(refsParam), true); } + + private prepareListenerParameter(tagName: string, outputAst: t.BoundEvent): () => o.Expression[] { + const evName = sanitizeIdentifier(outputAst.name); + const functionName = `${this.templateName}_${tagName}_${evName}_listener`; + + return () => { + + const listenerScope = this._bindingScope.nestedScope(this._bindingScope.bindingLevel); + + const bindingExpr = convertActionBinding( + listenerScope, o.variable(CONTEXT_NAME), outputAst.handler, 'b', + () => error('Unexpected interpolation')); + + const statements = [ + ...listenerScope.restoreViewStatement(), ...listenerScope.variableDeclarations(), + ...bindingExpr.render3Stmts + ]; + + const handler = o.fn( + [new o.FnParam('$event', o.DYNAMIC_TYPE)], statements, o.INFERRED_TYPE, null, + functionName); + + return [o.literal(outputAst.name), handler]; + }; + } } class ValueConverter extends AstMemoryEfficientTransformer { diff --git a/packages/core/src/render3/STATUS.md b/packages/core/src/render3/STATUS.md index 50cb4bffea1a1..b1bff348e0935 100644 --- a/packages/core/src/render3/STATUS.md +++ b/packages/core/src/render3/STATUS.md @@ -158,7 +158,7 @@ The goal is for the `@Component` (and friends) to be the compiler of template. S | `
` | ❌ | ❌ | ❌ | | `
` | ❌ | ❌ | ❌ | | [`
`][gh23560] | ✅ | ✅ | ✅ | -| [``][gh23561] | ❌ | ❌ | ❌ | +| [``][gh23561] | ✅ | ✅ | ✅ | | [``][gh24381] | ✅ | ✅ | ✅ | [gh23560]: https://github.com/angular/angular/issues/23560