Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(ivy): support directive outputs on ng-template #25717

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -401,4 +401,43 @@ describe('compiler compliance: template', () => {

expectEmit(result.source, template, 'Incorrect template');
});

it('should support directive outputs on <ng-template>', () => {

const files = {
app: {
'spec.ts': `
import {Component, NgModule} from '@angular/core';

@Component({
selector: 'my-component',
template: '<ng-template (outDirective)="$event.doSth()"></ng-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');

});
});
57 changes: 35 additions & 22 deletions packages/compiler/src/render3/view/template.ts
Expand Up @@ -516,28 +516,9 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, 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));
});
}

Expand Down Expand Up @@ -736,6 +717,13 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, 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.
Expand Down Expand Up @@ -906,6 +894,31 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, 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 {
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/render3/STATUS.md
Expand Up @@ -158,7 +158,7 @@ The goal is for the `@Component` (and friends) to be the compiler of template. S
| `<div (keyup.enter)>` | ❌ | ❌ | ❌ |
| `<div (hammer.js)>` | ❌ | ❌ | ❌ |
| [`<div (directiveOut)>`][gh23560] | ✅ | ✅ | ✅ |
| [`<ng-template (directiveOut)>`][gh23561] | | | |
| [`<ng-template (directiveOut)>`][gh23561] | | | |
| [`<ng-container>`][gh24381] | ✅ | ✅ | ✅ |

[gh23560]: https://github.com/angular/angular/issues/23560
Expand Down