feat(ivy): add support to template local refs in the compiler (#25576)

Fixes #23316

PR Close #25576
This commit is contained in:
Pawel Kozlowski 2018-08-20 14:23:17 +02:00 committed by Jason Aden
parent b05d4a5007
commit 11e2d9da1a
4 changed files with 83 additions and 24 deletions

View File

@ -359,4 +359,45 @@ describe('compiler compliance: template', () => {
expectEmit(result.source, template, 'Incorrect template'); expectEmit(result.source, template, 'Incorrect template');
}); });
it('should support local refs on <ng-template>', () => {
const files = {
app: {
'spec.ts': `
import {Component, NgModule} from '@angular/core';
@Component({
selector: 'my-component',
template: '<ng-template #foo>some-content</ng-template>';
})
export class MyComponent {}
@NgModule({declarations: [MyComponent]})
export class MyModule {}
`
}
};
const template = `
const _c0 = ["foo", ""];
function Template_0(rf, ctx) {
if (rf & 1) {
$i0$.ɵtext(0, "some-content");
}
}
// ...
template: function MyComponent_Template(rf, ctx) {
if (rf & 1) {
$i0$.ɵtemplate(0, Template_0, 1, null, null, _c0, i0.ɵtemplateRefExtractor);
}
}`;
const result = compile(files, angularFiles);
expectEmit(result.source, template, 'Incorrect template');
});
}); });

View File

@ -118,6 +118,9 @@ export class Identifiers {
static directiveInject: o.ExternalReference = {name: 'ɵdirectiveInject', moduleName: CORE}; static directiveInject: o.ExternalReference = {name: 'ɵdirectiveInject', moduleName: CORE};
static templateRefExtractor:
o.ExternalReference = {name: 'ɵtemplateRefExtractor', moduleName: CORE};
static defineBase: o.ExternalReference = {name: 'ɵdefineBase', moduleName: CORE}; static defineBase: o.ExternalReference = {name: 'ɵdefineBase', moduleName: CORE};
static BaseDef: o.ExternalReference = { static BaseDef: o.ExternalReference = {

View File

@ -457,30 +457,8 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
o.TYPED_NULL_EXPR; o.TYPED_NULL_EXPR;
parameters.push(attrArg); parameters.push(attrArg);
if (element.references && element.references.length > 0) { // local refs (ex.: <div #foo #bar="baz">)
const references = flatten(element.references.map(reference => { parameters.push(this.prepareRefsParameter(element.references));
const slot = this.allocateDataSlot();
// Generate the update temporary.
const variableName = this._bindingScope.freshReferenceName();
const retrievalLevel = this.level;
const lhs = o.variable(variableName);
this._bindingScope.set(
retrievalLevel, reference.name, lhs, DeclarationPriority.DEFAULT,
(scope: BindingScope, relativeLevel: number) => {
// e.g. x(2);
const nextContextStmt =
relativeLevel > 0 ? [generateNextContextExpr(relativeLevel).toStmt()] : [];
// e.g. const $foo$ = r(1);
const refExpr = lhs.set(o.importExpr(R3.reference).callFn([o.literal(slot)]));
return nextContextStmt.concat(refExpr.toConstDecl());
});
return [reference.name, reference.value];
}));
parameters.push(this.constantPool.getConstLiteral(asLiteral(references), true));
} else {
parameters.push(o.TYPED_NULL_EXPR);
}
const wasInNamespace = this._namespace; const wasInNamespace = this._namespace;
const currentNamespace = this.getNamespaceInstruction(namespaceKey); const currentNamespace = this.getNamespaceInstruction(namespaceKey);
@ -730,6 +708,14 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
if (attributeNames.length) { if (attributeNames.length) {
parameters.push(this.constantPool.getConstLiteral(o.literalArr(attributeNames), true)); parameters.push(this.constantPool.getConstLiteral(o.literalArr(attributeNames), true));
} else {
parameters.push(o.TYPED_NULL_EXPR);
}
// local refs (ex.: <ng-template #foo>)
if (template.references && template.references.length) {
parameters.push(this.prepareRefsParameter(template.references));
parameters.push(o.importExpr(R3.templateRefExtractor));
} }
// e.g. p(1, 'forOf', ɵbind(ctx.items)); // e.g. p(1, 'forOf', ɵbind(ctx.items));
@ -858,6 +844,34 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
return value instanceof Interpolation || skipBindFn ? valExpr : return value instanceof Interpolation || skipBindFn ? valExpr :
o.importExpr(R3.bind).callFn([valExpr]); o.importExpr(R3.bind).callFn([valExpr]);
} }
private prepareRefsParameter(references: t.Reference[]): o.Expression {
if (!references || references.length === 0) {
return o.TYPED_NULL_EXPR;
}
const refsParam = flatten(references.map(reference => {
const slot = this.allocateDataSlot();
// Generate the update temporary.
const variableName = this._bindingScope.freshReferenceName();
const retrievalLevel = this.level;
const lhs = o.variable(variableName);
this._bindingScope.set(
retrievalLevel, reference.name, lhs, DeclarationPriority.DEFAULT,
(scope: BindingScope, relativeLevel: number) => {
// e.g. x(2);
const nextContextStmt =
relativeLevel > 0 ? [generateNextContextExpr(relativeLevel).toStmt()] : [];
// e.g. const $foo$ = r(1);
const refExpr = lhs.set(o.importExpr(R3.reference).callFn([o.literal(slot)]));
return nextContextStmt.concat(refExpr.toConstDecl());
});
return [reference.name, reference.value];
}));
return this.constantPool.getConstLiteral(asLiteral(refsParam), true);
}
} }
class ValueConverter extends AstMemoryEfficientTransformer { class ValueConverter extends AstMemoryEfficientTransformer {

View File

@ -34,6 +34,7 @@ export const angularCoreEnv: {[name: string]: Function} = {
'ɵinjectElementRef': r3.injectElementRef, 'ɵinjectElementRef': r3.injectElementRef,
'ɵinjectTemplateRef': r3.injectTemplateRef, 'ɵinjectTemplateRef': r3.injectTemplateRef,
'ɵinjectViewContainerRef': r3.injectViewContainerRef, 'ɵinjectViewContainerRef': r3.injectViewContainerRef,
'ɵtemplateRefExtractor': r3.templateRefExtractor,
'ɵNgOnChangesFeature': r3.NgOnChangesFeature, 'ɵNgOnChangesFeature': r3.NgOnChangesFeature,
'ɵPublicFeature': r3.PublicFeature, 'ɵPublicFeature': r3.PublicFeature,
'ɵInheritDefinitionFeature': r3.InheritDefinitionFeature, 'ɵInheritDefinitionFeature': r3.InheritDefinitionFeature,