fix(ivy): support directive outputs on ng-template (#25717)
Compiler part of #25698 Fixes #25697 PR Close #25717
This commit is contained in:

committed by
Misko Hevery

parent
34be51898d
commit
6def18a95e
@ -401,4 +401,43 @@ describe('compiler compliance: template', () => {
|
|||||||
|
|
||||||
expectEmit(result.source, template, 'Incorrect 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');
|
||||||
|
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -516,28 +516,9 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
|||||||
|
|
||||||
// Generate Listeners (outputs)
|
// Generate Listeners (outputs)
|
||||||
element.outputs.forEach((outputAst: t.BoundEvent) => {
|
element.outputs.forEach((outputAst: t.BoundEvent) => {
|
||||||
const elName = sanitizeIdentifier(element.name);
|
this.creationInstruction(
|
||||||
const evName = sanitizeIdentifier(outputAst.name);
|
outputAst.sourceSpan, R3.listener,
|
||||||
const functionName = `${this.templateName}_${elName}_${evName}_listener`;
|
this.prepareListenerParameter(element.name, outputAst));
|
||||||
|
|
||||||
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];
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -736,6 +717,13 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
|||||||
o.literal(templateVisitor.getVarCount()));
|
o.literal(templateVisitor.getVarCount()));
|
||||||
return trimTrailingNulls(parameters);
|
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.
|
// These should be handled in the template or element directly.
|
||||||
@ -906,6 +894,31 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
|||||||
|
|
||||||
return this.constantPool.getConstLiteral(asLiteral(refsParam), true);
|
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];
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ValueConverter extends AstMemoryEfficientTransformer {
|
export class ValueConverter extends AstMemoryEfficientTransformer {
|
||||||
|
@ -158,7 +158,7 @@ The goal is for the `@Component` (and friends) to be the compiler of template. S
|
|||||||
| `<div (keyup.enter)>` | ❌ | ❌ | ❌ |
|
| `<div (keyup.enter)>` | ❌ | ❌ | ❌ |
|
||||||
| `<div (hammer.js)>` | ❌ | ❌ | ❌ |
|
| `<div (hammer.js)>` | ❌ | ❌ | ❌ |
|
||||||
| [`<div (directiveOut)>`][gh23560] | ✅ | ✅ | ✅ |
|
| [`<div (directiveOut)>`][gh23560] | ✅ | ✅ | ✅ |
|
||||||
| [`<ng-template (directiveOut)>`][gh23561] | ❌ | ❌ | ❌ |
|
| [`<ng-template (directiveOut)>`][gh23561] | ✅ | ✅ | ✅ |
|
||||||
| [`<ng-container>`][gh24381] | ✅ | ✅ | ✅ |
|
| [`<ng-container>`][gh24381] | ✅ | ✅ | ✅ |
|
||||||
|
|
||||||
[gh23560]: https://github.com/angular/angular/issues/23560
|
[gh23560]: https://github.com/angular/angular/issues/23560
|
||||||
|
Reference in New Issue
Block a user