From fe14f180a669a6fd0651ea8a1fd084cbb04777ca Mon Sep 17 00:00:00 2001 From: Kara Erickson Date: Wed, 18 Jul 2018 01:59:49 +0000 Subject: [PATCH] fix(compiler): update compiler to flatten nested template fns (#24943) PR Close #24943 --- .../compliance/r3_compiler_compliance_spec.ts | 216 +++++++---- .../r3_view_compiler_template_spec.ts | 85 ++--- .../compiler/src/render3/r3_identifiers.ts | 2 + .../compiler/src/render3/view/template.ts | 274 ++++++++------ .../core/src/core_render3_private_export.ts | 1 + packages/core/src/render3/di.ts | 2 +- packages/core/src/render3/index.ts | 2 + packages/core/src/render3/instructions.ts | 141 +++++--- .../core/src/render3/interfaces/definition.ts | 12 +- packages/core/src/render3/interfaces/view.ts | 22 +- packages/core/src/render3/jit/environment.ts | 1 + packages/core/src/render3/view_ref.ts | 28 ++ .../hello_world/bundle.golden_symbols.json | 10 +- .../bundling/todo/bundle.golden_symbols.json | 31 +- .../test/render3/common_integration_spec.ts | 340 +++++++++--------- packages/core/test/render3/component_spec.ts | 39 +- packages/core/test/render3/content_spec.ts | 26 +- packages/core/test/render3/exports_spec.ts | 103 ++++-- .../test/render3/view_container_ref_spec.ts | 85 ++--- 19 files changed, 863 insertions(+), 557 deletions(-) diff --git a/packages/compiler-cli/test/compliance/r3_compiler_compliance_spec.ts b/packages/compiler-cli/test/compliance/r3_compiler_compliance_spec.ts index 049c9ba8d9..424ea3c0b9 100644 --- a/packages/compiler-cli/test/compliance/r3_compiler_compliance_spec.ts +++ b/packages/compiler-cli/test/compliance/r3_compiler_compliance_spec.ts @@ -541,6 +541,17 @@ describe('compiler compliance', () => { const MyComponentDefinition = ` const $c1$ = ["foo", ""]; const $c2$ = ["if", ""]; + function MyComponent_li_Template_2(rf, ctx0, ctx) { + if (rf & 1) { + $r3$.ɵE(0, "li"); + $r3$.ɵT(1); + $r3$.ɵe(); + } + if (rf & 2) { + const $foo$ = $r3$.ɵr(1, 1); + $r3$.ɵt(1, $r3$.ɵi2("", ctx.salutation, " ", $foo$, "")); + } + } … MyComponent.ngComponentDef = $r3$.ɵdefineComponent({ type: MyComponent, @@ -552,17 +563,6 @@ describe('compiler compliance', () => { $r3$.ɵC(2, MyComponent_li_Template_2, null, $c2$); $r3$.ɵe(); } - const $foo$ = $r3$.ɵld(1); - function MyComponent_li_Template_2(rf, ctx0) { - if (rf & 1) { - $r3$.ɵE(0, "li"); - $r3$.ɵT(1); - $r3$.ɵe(); - } - if (rf & 2) { - $r3$.ɵt(1, $r3$.ɵi2("", ctx.salutation, " ", $foo$, "")); - } - } }, directives:[IfDirective] });`; @@ -1173,8 +1173,8 @@ describe('compiler compliance', () => { $r3$.ɵEe(0, "input", null, $c1$); $r3$.ɵT(2); } - const $user$ = $r3$.ɵld(1); if (rf & 2) { + const $user$ = $r3$.ɵld(1); $r3$.ɵt(2, $r3$.ɵi1("Hello ", $user$.value, "!")); } } @@ -1187,6 +1187,97 @@ describe('compiler compliance', () => { expectEmit(source, MyComponentDefinition, 'Incorrect MyComponent.ngComponentDef'); }); + it('local references in nested views', () => { + const files = { + app: { + 'spec.ts': ` + import {Component, Directive, NgModule, TemplateRef} from '@angular/core'; + + @Directive({selector: '[if]'}) + export class IfDirective { + constructor(template: TemplateRef) { } + } + + @Component({ + selector: 'my-component', + template: \` +
+ {{foo}} +
+ {{foo}}-{{bar}} + {{foo}}-{{bar}}-{{baz}} + +
+
+ \` + }) + export class MyComponent {} + + @NgModule({declarations: [IfDirective, MyComponent]}) + export class MyModule {} + ` + } + }; + + const MyComponentDefinition = ` + const $c1$ = ["foo", ""]; + const $c2$ = ["if", ""]; + const $c3$ = ["baz", ""]; + const $c4$ = ["bar", ""]; + function MyComponent_div_span_Template_2(rf, ctx1, ctx0, ctx) { + if (rf & 1) { + $r3$.ɵE(0, "span"); + $r3$.ɵT(1); + $r3$.ɵe(); + } + if (rf & 2) { + const $foo$ = $r3$.ɵr(2, 1); + const $bar$ = $r3$.ɵr(1, 4); + const $baz$ = $r3$.ɵr(2, 5); + $r3$.ɵt(1, $r3$.ɵi3("", $foo$, "-", $bar$, "-", $baz$, "")); + } + } + function MyComponent_div_Template_3(rf, ctx0, ctx) { + if (rf & 1) { + $r3$.ɵE(0, "div"); + $r3$.ɵT(1); + $r3$.ɵC(2, MyComponent_div_span_Template_2, null, $c2$); + $r3$.ɵEe(3, "span", null, $c4$); + $r3$.ɵe(); + } + if (rf & 2) { + const $foo$ = $r3$.ɵr(1, 1); + const $bar$ = $r3$.ɵld(4); + $r3$.ɵt(1, $r3$.ɵi2(" ", $foo$, "-", $bar$, " ")); + } + } + … + MyComponent.ngComponentDef = $r3$.ɵdefineComponent({ + type: MyComponent, + selectors: [["my-component"]], + factory: function MyComponent_Factory() { return new MyComponent(); }, + template: function MyComponent_Template(rf, ctx) { + if (rf & 1) { + $r3$.ɵEe(0, "div", null, $c1$); + $r3$.ɵT(2); + $r3$.ɵC(3, MyComponent_div_Template_3, null, $c2$); + $r3$.ɵEe(4, "div", null, $c3$); + } + if (rf & 2) { + const $foo$ = $r3$.ɵld(1); + $r3$.ɵt(2, $r3$.ɵi1(" ", $foo$, " ")); + } + }, + directives:[IfDirective] + });`; + + const result = compile(files, angularFiles); + const source = result.source; + + expectEmit(source, MyComponentDefinition, 'Incorrect MyComponent.ngComponentDef'); + + }); + describe('lifecycle hooks', () => { const files = { app: { @@ -1361,6 +1452,14 @@ describe('compiler compliance', () => { const MyComponentDefinition = ` const $_c0$ = ["for","","forOf",""]; + function MyComponent__svg_g_Template_1(rf, ctx0, ctx) { + if (rf & 1) { + $r3$.ɵNS(); + $r3$.ɵE(0,"g"); + $r3$.ɵEe(1,"circle"); + $r3$.ɵe(); + } + } … MyComponent.ngComponentDef = $r3$.ɵdefineComponent({ type: MyComponent, @@ -1374,14 +1473,6 @@ describe('compiler compliance', () => { $r3$.ɵe(); } if (rf & 2) { $r3$.ɵp(1,"forOf",$r3$.ɵb(ctx.items)); } - function MyComponent__svg_g_Template_1(rf, ctx0) { - if (rf & 1) { - $r3$.ɵNS(); - $r3$.ɵE(0,"g"); - $r3$.ɵEe(1,"circle"); - $r3$.ɵe(); - } - } }, directives: [ForOfDirective] }); @@ -1434,6 +1525,17 @@ describe('compiler compliance', () => { const MyComponentDefinition = ` const $_c0$ = ["for","","forOf",""]; + function MyComponent_li_Template_1(rf, ctx0, ctx) { + if (rf & 1) { + $r3$.ɵE(0, "li"); + $r3$.ɵT(1); + $r3$.ɵe(); + } + if (rf & 2) { + const $item$ = ctx0.$implicit; + $r3$.ɵt(1, $r3$.ɵi1("", $item$.name, "")); + } + } … MyComponent.ngComponentDef = $r3$.ɵdefineComponent({ type: MyComponent, @@ -1448,18 +1550,6 @@ describe('compiler compliance', () => { if (rf & 2) { $r3$.ɵp(1, "forOf", $r3$.ɵb(ctx.items)); } - - function MyComponent_li_Template_1(rf, ctx0) { - if (rf & 1) { - $r3$.ɵE(0, "li"); - $r3$.ɵT(1); - $r3$.ɵe(); - } - if (rf & 2) { - const $item$ = ctx0.$implicit; - $r3$.ɵt(1, $r3$.ɵi1("", $item$.name, "")); - } - } }, directives: [ForOfDirective] }); @@ -1512,6 +1602,37 @@ describe('compiler compliance', () => { const MyComponentDefinition = ` const $c1$ = ["for", "", "forOf", ""]; + function MyComponent_li_li_Template_4(rf, ctx1, ctx0, ctx) { + if (rf & 1) { + $r3$.ɵE(0, "li"); + $r3$.ɵT(1); + $r3$.ɵe(); + } + if (rf & 2) { + const $item$ = ctx0.$implicit; + const $info$ = ctx1.$implicit; + $r3$.ɵt(1, $r3$.ɵi2(" ", $item$.name, ": ", $info$.description, " ")); + } + } + + function MyComponent_li_Template_1(rf, ctx0, ctx) { + if (rf & 1) { + $r3$.ɵE(0, "li"); + $r3$.ɵE(1, "div"); + $r3$.ɵT(2); + $r3$.ɵe(); + $r3$.ɵE(3, "ul"); + $r3$.ɵC(4, MyComponent_li_li_Template_4, null, $c1$); + $r3$.ɵe(); + $r3$.ɵe(); + } + if (rf & 2) { + const $item$ = ctx0.$implicit; + $r3$.ɵt(2, $r3$.ɵi1("", IDENT.name, "")); + $r3$.ɵp(4, "forOf", $r3$.ɵb(IDENT.infos)); + } + } + … MyComponent.ngComponentDef = $r3$.ɵdefineComponent({ type: MyComponent, @@ -1526,37 +1647,6 @@ describe('compiler compliance', () => { if (rf & 2) { $r3$.ɵp(1, "forOf", $r3$.ɵb(ctx.items)); } - - function MyComponent_li_Template_1(rf, ctx0) { - if (rf & 1) { - $r3$.ɵE(0, "li"); - $r3$.ɵE(1, "div"); - $r3$.ɵT(2); - $r3$.ɵe(); - $r3$.ɵE(3, "ul"); - $r3$.ɵC(4, MyComponent_li_li_Template_4, null, $c1$); - $r3$.ɵe(); - $r3$.ɵe(); - } - if (rf & 2) { - const $item$ = ctx0.$implicit; - $r3$.ɵt(2, $r3$.ɵi1("", IDENT.name, "")); - $r3$.ɵp(4, "forOf", $r3$.ɵb(IDENT.infos)); - } - - function MyComponent_li_li_Template_4(rf, ctx1) { - if (rf & 1) { - $r3$.ɵE(0, "li"); - $r3$.ɵT(1); - $r3$.ɵe(); - } - if (rf & 2) { - const $item$ = ctx0.$implicit; - const $info$ = ctx1.$implicit; - $r3$.ɵt(1, $r3$.ɵi2(" ", $item$.name, ": ", $info$.description, " ")); - } - } - } }, directives: [ForOfDirective] });`; 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 8c7d522ce8..3cfc3a2ecf 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 @@ -52,6 +52,49 @@ describe('compiler compliance: template', () => { // The template should look like this (where IDENT is a wild card for an identifier): const template = ` const $c0$ = ["ngFor","","ngForOf",""]; + function MyComponent_ul_li_div_Template_1(rf, $ctx2$, $ctx1$, $ctx0$, $ctx$) { + if (rf & 1) { + $i0$.ɵE(0, "div"); + $i0$.ɵL("click", function MyComponent_ul_li_div_Template_1_div_click_listener($event){ + const $outer$ = $ctx0$.$implicit; + const $middle$ = $ctx1$.$implicit; + const $inner$ = $ctx2$.$implicit; + return ctx.onClick($outer$, $middle$, $inner$); + }); + $i0$.ɵT(1); + $i0$.ɵe(); + } + if (rf & 2) { + const $outer$ = $ctx0$.$implicit; + const $middle$ = $ctx1$.$implicit; + const $inner$ = $ctx2$.$implicit; + $i0$.ɵp(0, "title", $i0$.ɵb(ctx.format($outer$, $middle$, $inner$, $ctx$.component))); + $i0$.ɵt(1, $i0$.ɵi1(" ", ctx.format($outer$, $middle$, $inner$, $ctx$.component), " ")); + } + } + + function MyComponent_ul_li_Template_1(rf, $ctx1$, $ctx0$, $ctx$) { + if (rf & 1) { + $i0$.ɵE(0, "li"); + $i0$.ɵC(1, MyComponent_ul_li_div_Template_1, null, _c0); + $i0$.ɵe(); + } + if (rf & 2) { + $i0$.ɵp(1, "ngForOf", $i0$.ɵb($ctx$.items)); + } + } + + function MyComponent_ul_Template_0(rf, $ctx0$, $ctx$) { + if (rf & 1) { + $i0$.ɵE(0, "ul"); + $i0$.ɵC(1, MyComponent_ul_li_Template_1, null, _c0); + $i0$.ɵe(); + } + if (rf & 2) { + const $outer$ = $ctx0$.$implicit; + $i0$.ɵp(1, "ngForOf", $i0$.ɵb($outer$.items)); + } + } // ... template:function MyComponent_Template(rf, $ctx$){ if (rf & 1) { @@ -60,48 +103,6 @@ describe('compiler compliance: template', () => { if (rf & 2) { $i0$.ɵp(0, "ngForOf", $i0$.ɵb($ctx$.items)); } - - function MyComponent_ul_Template_0(rf, $ctx0$) { - if (rf & 1) { - $i0$.ɵE(0, "ul"); - $i0$.ɵC(1, MyComponent_ul_li_Template_1, null, _c0); - $i0$.ɵe(); - } - if (rf & 2) { - const $outer$ = $ctx0$.$implicit; - $i0$.ɵp(1, "ngForOf", $i0$.ɵb($outer$.items)); - } - function MyComponent_ul_li_Template_1(rf, $ctx1$) { - if (rf & 1) { - $i0$.ɵE(0, "li"); - $i0$.ɵC(1, MyComponent_ul_li_div_Template_1, null, _c0); - $i0$.ɵe(); - } - if (rf & 2) { - $i0$.ɵp(1, "ngForOf", $i0$.ɵb($ctx$.items)); - } - function MyComponent_ul_li_div_Template_1(rf, $ctx2$) { - if (rf & 1) { - $i0$.ɵE(0, "div"); - $i0$.ɵL("click", function MyComponent_ul_li_div_Template_1_div_click_listener($event){ - const $outer$ = $ctx0$.$implicit; - const $middle$ = $ctx1$.$implicit; - const $inner$ = $ctx2$.$implicit; - return ctx.onClick($outer$, $middle$, $inner$); - }); - $i0$.ɵT(1); - $i0$.ɵe(); - } - if (rf & 2) { - const $outer$ = $ctx0$.$implicit; - const $middle$ = $ctx1$.$implicit; - const $inner$ = $ctx2$.$implicit; - $i0$.ɵp(0, "title", $i0$.ɵb(ctx.format($outer$, $middle$, $inner$, $ctx$.component))); - $i0$.ɵt(1, $i0$.ɵi1(" ", ctx.format($outer$, $middle$, $inner$, $ctx$.component), " ")); - } - } - } - } }`; const result = compile(files, angularFiles); diff --git a/packages/compiler/src/render3/r3_identifiers.ts b/packages/compiler/src/render3/r3_identifiers.ts index 5e1aaabd46..7acb4c0324 100644 --- a/packages/compiler/src/render3/r3_identifiers.ts +++ b/packages/compiler/src/render3/r3_identifiers.ts @@ -87,6 +87,8 @@ export class Identifiers { static projection: o.ExternalReference = {name: 'ɵP', moduleName: CORE}; static projectionDef: o.ExternalReference = {name: 'ɵpD', moduleName: CORE}; + static reference: o.ExternalReference = {name: 'ɵr', moduleName: CORE}; + static inject: o.ExternalReference = {name: 'inject', moduleName: CORE}; static injectAttribute: o.ExternalReference = {name: 'ɵinjectAttribute', moduleName: CORE}; diff --git a/packages/compiler/src/render3/view/template.ts b/packages/compiler/src/render3/view/template.ts index cdb8f76452..dff44b5d3e 100644 --- a/packages/compiler/src/render3/view/template.ts +++ b/packages/compiler/src/render3/view/template.ts @@ -58,8 +58,8 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver private _prefixCode: o.Statement[] = []; private _creationCode: o.Statement[] = []; private _variableCode: o.Statement[] = []; - private _bindingCode: o.Statement[] = []; - private _postfixCode: o.Statement[] = []; + private _bindingCode: (() => o.Statement)[] = []; + private _nestedTemplates: (() => void)[] = []; private _valueConverter: ValueConverter; private _unsupported = unsupported; private _bindingScope: BindingScope; @@ -84,9 +84,9 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver // function) this._dataIndex = viewQueries.length; this._bindingScope = - parentBindingScope.nestedScope((lhsVar: o.ReadVarExpr, expression: o.Expression) => { - this._bindingCode.push( - lhsVar.set(expression).toDeclStmt(o.INFERRED_TYPE, [o.StmtModifier.Final])); + parentBindingScope.nestedScope(level, (lhsVar: o.ReadVarExpr, rhsExpr: o.Expression) => { + this._variableCode.push( + lhsVar.set(rhsExpr).toDeclStmt(o.INFERRED_TYPE, [o.StmtModifier.Final])); }); this._valueConverter = new ValueConverter( constantPool, () => this.allocateDataSlot(), @@ -106,7 +106,7 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver nodes: t.Node[], variables: t.Variable[], hasNgContent: boolean = false, ngContentSelectors: string[] = []): o.FunctionExpr { if (this._namespace !== R3.namespaceHTML) { - this.instruction(this._creationCode, null, this._namespace); + this.creationInstruction(null, this._namespace); } // Create variable bindings @@ -132,24 +132,24 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver parameters.push(parsed, unParsed); } - this.instruction(this._creationCode, null, R3.projectionDef, ...parameters); + this.creationInstruction(null, R3.projectionDef, ...parameters); } t.visitAll(this, nodes); - if (this._pureFunctionSlots > 0) { - this.instruction( - this._creationCode, null, R3.reserveSlots, o.literal(this._pureFunctionSlots)); - } - const creationCode = this._creationCode.length > 0 ? [renderFlagCheckIfStmt(core.RenderFlags.Create, this._creationCode)] : []; const updateCode = this._bindingCode.length > 0 ? - [renderFlagCheckIfStmt(core.RenderFlags.Update, this._bindingCode)] : + [renderFlagCheckIfStmt(core.RenderFlags.Update, this._variableCode.concat( + this._variableCode.concat(this._bindingCode.map((fn: () => o.Statement) => fn()))))] : []; + if (this._pureFunctionSlots > 0) { + this.creationInstruction(null, R3.reserveSlots, o.literal(this._pureFunctionSlots)); + } + // Generate maps of placeholder name to node indexes // TODO(vicb): This is a WIP, not fully supported yet for (const phToNodeIdx of this._phToNodeIdxes) { @@ -163,19 +163,21 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver } } + this._nestedTemplates.forEach(buildTemplateFn => buildTemplateFn()); + return o.fn( - [new o.FnParam(RENDER_FLAGS, o.NUMBER_TYPE), new o.FnParam(this.contextParameter, null)], + // i.e. (rf: RenderFlags, ctx0: any, ctx: any) + [ + new o.FnParam(RENDER_FLAGS, o.NUMBER_TYPE), ...this.getNestedContexts(), + new o.FnParam(CONTEXT_NAME, null) + ], [ // Temporary variable declarations for query refresh (i.e. let _t: any;) ...this._prefixCode, // Creating mode (i.e. if (rf & RenderFlags.Create) { ... }) ...creationCode, - // Temporary variable declarations for local refs (i.e. const tmp = ld(1) as any) - ...this._variableCode, // Binding and refresh mode (i.e. if (rf & RenderFlags.Update) {...}) ...updateCode, - // Nested templates (i.e. function CompTemplate() {}) - ...this._postfixCode ], o.INFERRED_TYPE, null, this.templateName); } @@ -203,7 +205,7 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver parameters.push(o.literal(selectorIndex)); } - this.instruction(this._creationCode, ngContent.sourceSpan, R3.projection, ...parameters); + this.creationInstruction(ngContent.sourceSpan, R3.projection, ...parameters); } @@ -220,12 +222,23 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver addNamespaceInstruction(nsInstruction: o.ExternalReference, element: t.Element) { this._namespace = nsInstruction; - this.instruction(this._creationCode, element.sourceSpan, nsInstruction); + this.creationInstruction(element.sourceSpan, nsInstruction); + } + + getNestedContexts(): o.FnParam[] { + const nestedContexts = []; + let nestingLevel = this.level - 1; + + while (nestingLevel >= 0) { + nestedContexts.push(new o.FnParam(`ctx${nestingLevel}`, null)); + nestingLevel--; + } + + return nestedContexts; } visitElement(element: t.Element) { const elementIndex = this.allocateDataSlot(); - const referenceDataSlots = new Map(); const wasInI18nSection = this._inI18nSection; const outputAttrs: {[name: string]: string} = {}; @@ -412,13 +425,19 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver if (element.references && element.references.length > 0) { const references = flatten(element.references.map(reference => { const slot = this.allocateDataSlot(); - referenceDataSlots.set(reference.name, slot); // Generate the update temporary. const variableName = this._bindingScope.freshReferenceName(); - this._variableCode.push(o.variable(variableName, o.INFERRED_TYPE) - .set(o.importExpr(R3.load).callFn([o.literal(slot)])) - .toDeclStmt(o.INFERRED_TYPE, [o.StmtModifier.Final])); - this._bindingScope.set(reference.name, o.variable(variableName)); + // When the ref's binding is processed, we'll either generate a load() or a reference() + // instruction depending on the nesting level of the binding relative to the reference def. + const refLevel = this.level; + this._bindingScope.set( + reference.name, o.variable(variableName), undefined, (bindingLevel: number) => { + return bindingLevel === refLevel ? + o.importExpr(R3.load).callFn([o.literal(slot)]) : + o.importExpr(R3.reference).callFn([ + o.literal(bindingLevel - refLevel), o.literal(slot) + ]); + }); return [reference.name, reference.value]; })); parameters.push(this.constantPool.getConstLiteral(asLiteral(references), true)); @@ -446,16 +465,14 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver !hasStylingInstructions && element.children.length === 0 && element.outputs.length === 0; if (createSelfClosingInstruction) { - this.instruction( - this._creationCode, element.sourceSpan, R3.element, ...trimTrailingNulls(parameters)); + this.creationInstruction(element.sourceSpan, R3.element, ...trimTrailingNulls(parameters)); } else { // Generate the instruction create element instruction if (i18nMessages.length > 0) { this._creationCode.push(...i18nMessages); } - this.instruction( - this._creationCode, element.sourceSpan, R3.elementStart, - ...trimTrailingNulls(parameters)); + this.creationInstruction( + element.sourceSpan, R3.elementStart, ...trimTrailingNulls(parameters)); // initial styling for static style="..." attributes if (hasStylingInstructions) { @@ -499,8 +516,8 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver const evName = sanitizeIdentifier(outputAst.name); const functionName = `${this.templateName}_${elName}_${evName}_listener`; const localVars: o.Statement[] = []; - const bindingScope = - this._bindingScope.nestedScope((lhsVar: o.ReadVarExpr, rhsExpression: o.Expression) => { + const bindingScope = this._bindingScope.nestedScope( + this.level + 1, (lhsVar: o.ReadVarExpr, rhsExpression: o.Expression) => { localVars.push( lhsVar.set(rhsExpression).toDeclStmt(o.INFERRED_TYPE, [o.StmtModifier.Final])); }); @@ -510,9 +527,8 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver const handler = o.fn( [new o.FnParam('$event', o.DYNAMIC_TYPE)], [...localVars, ...bindingExpr.render3Stmts], o.INFERRED_TYPE, null, functionName); - this.instruction( - this._creationCode, outputAst.sourceSpan, R3.listener, o.literal(outputAst.name), - handler); + this.creationInstruction( + outputAst.sourceSpan, R3.listener, o.literal(outputAst.name), handler); }); } @@ -528,17 +544,21 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver const stylingInput = mapBasedStyleInput || mapBasedClassInput; if (stylingInput) { const params: o.Expression[] = []; + let value: AST; if (mapBasedClassInput) { - params.push(this.convertPropertyBinding(implicit, mapBasedClassInput.value, true)); + value = mapBasedClassInput.value.visit(this._valueConverter); } else if (mapBasedStyleInput) { params.push(o.NULL_EXPR); } + if (mapBasedStyleInput) { - params.push(this.convertPropertyBinding(implicit, mapBasedStyleInput.value, true)); + value = mapBasedStyleInput.value.visit(this._valueConverter); } - this.instruction( - this._bindingCode, stylingInput.sourceSpan, R3.elementStylingMap, indexLiteral, - ...params); + + this.updateInstruction(stylingInput.sourceSpan, R3.elementStylingMap, () => { + params.push(this.convertPropertyBinding(implicit, value, true)); + return [indexLiteral, ...params]; + }); } let lastInputCommand: t.BoundAttribute|null = null; @@ -546,18 +566,19 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver let i = mapBasedStyleInput ? 1 : 0; for (i; i < styleInputs.length; i++) { const input = styleInputs[i]; - const convertedBinding = this.convertPropertyBinding(implicit, input.value, true); - const params = [convertedBinding]; + const params: any[] = []; const sanitizationRef = resolveSanitizationFn(input, input.securityContext); - if (sanitizationRef) { - params.push(sanitizationRef); - } + if (sanitizationRef) params.push(sanitizationRef); const key = input.name; const styleIndex: number = stylesIndexMap[key] !; - this.instruction( - this._bindingCode, input.sourceSpan, R3.elementStyleProp, indexLiteral, - o.literal(styleIndex), ...params); + const value = input.value.visit(this._valueConverter); + this.updateInstruction(input.sourceSpan, R3.elementStyleProp, () => { + return [ + indexLiteral, o.literal(styleIndex), + this.convertPropertyBinding(implicit, value, true), ...params + ]; + }); } lastInputCommand = styleInputs[styleInputs.length - 1]; @@ -567,25 +588,26 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver let i = mapBasedClassInput ? 1 : 0; for (i; i < classInputs.length; i++) { const input = classInputs[i]; - const convertedBinding = this.convertPropertyBinding(implicit, input.value, true); - const params = [convertedBinding]; + const params: any[] = []; const sanitizationRef = resolveSanitizationFn(input, input.securityContext); - if (sanitizationRef) { - params.push(sanitizationRef); - } + if (sanitizationRef) params.push(sanitizationRef); const key = input.name; const classIndex: number = classesIndexMap[key] !; - this.instruction( - this._bindingCode, input.sourceSpan, R3.elementClassProp, indexLiteral, - o.literal(classIndex), ...params); + const value = input.value.visit(this._valueConverter); + this.updateInstruction(input.sourceSpan, R3.elementClassProp, () => { + return [ + indexLiteral, o.literal(classIndex), + this.convertPropertyBinding(implicit, value, true), ...params + ]; + }); } lastInputCommand = classInputs[classInputs.length - 1]; } - this.instruction( - this._bindingCode, lastInputCommand !.sourceSpan, R3.elementStylingApply, indexLiteral); + this.updateInstruction( + lastInputCommand !.sourceSpan, R3.elementStylingApply, () => [indexLiteral]); } // Generate element input bindings @@ -595,20 +617,20 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver return; } - const convertedBinding = this.convertPropertyBinding(implicit, input.value); - const instruction = mapBindingToInstruction(input.type); if (instruction) { - const params = [convertedBinding]; + const params: any[] = []; const sanitizationRef = resolveSanitizationFn(input, input.securityContext); - if (sanitizationRef) { - params.push(sanitizationRef); - } + if (sanitizationRef) params.push(sanitizationRef); // TODO(chuckj): runtime: security context? - this.instruction( - this._bindingCode, input.sourceSpan, instruction, o.literal(elementIndex), - o.literal(input.name), ...params); + const value = input.value.visit(this._valueConverter); + this.updateInstruction(input.sourceSpan, instruction, () => { + return [ + o.literal(elementIndex), o.literal(input.name), + this.convertPropertyBinding(implicit, value), ...params + ]; + }); } else { this._unsupported(`binding type ${input.type}`); } @@ -625,8 +647,7 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver if (!createSelfClosingInstruction) { // Finish element construction mode. - this.instruction( - this._creationCode, element.endSourceSpan || element.sourceSpan, R3.elementEnd); + this.creationInstruction(element.endSourceSpan || element.sourceSpan, R3.elementEnd); } // Restore the state before exiting this node @@ -675,17 +696,19 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver } // e.g. C(1, C1Template) - this.instruction( - this._creationCode, template.sourceSpan, R3.containerCreate, - ...trimTrailingNulls(parameters)); + this.creationInstruction( + template.sourceSpan, R3.containerCreate, ...trimTrailingNulls(parameters)); // e.g. p(1, 'forOf', ɵb(ctx.items)); const context = o.variable(CONTEXT_NAME); template.inputs.forEach(input => { - const convertedBinding = this.convertPropertyBinding(context, input.value); - this.instruction( - this._bindingCode, template.sourceSpan, R3.elementProperty, o.literal(templateIndex), - o.literal(input.name), convertedBinding); + const value = input.value.visit(this._valueConverter); + this.updateInstruction(template.sourceSpan, R3.elementProperty, () => { + return [ + o.literal(templateIndex), o.literal(input.name), + this.convertPropertyBinding(context, value) + ]; + }); }); // Create the template function @@ -693,9 +716,16 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver this.constantPool, templateContext, this._bindingScope, this.level + 1, contextName, templateName, [], this.directiveMatcher, this.directives, this.pipeTypeByName, this.pipes, this._namespace); - const templateFunctionExpr = - templateVisitor.buildTemplateFunction(template.children, template.variables); - this._postfixCode.push(templateFunctionExpr.toDeclStmt(templateName, null)); + + // Nested templates must not be visited until after their parent templates have completed + // processing, so they are queued here until after the initial pass. Otherwise, we wouldn't + // be able to support bindings in nested templates to local refs that occur after the + // template definition. e.g.
{{ foo }}
+ this._nestedTemplates.push(() => { + const templateFunctionExpr = + templateVisitor.buildTemplateFunction(template.children, template.variables); + this.constantPool.statements.push(templateFunctionExpr.toDeclStmt(templateName, null)); + }); } // These should be handled in the template or element directly. @@ -708,17 +738,17 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver visitBoundText(text: t.BoundText) { const nodeIndex = this.allocateDataSlot(); - this.instruction(this._creationCode, text.sourceSpan, R3.text, o.literal(nodeIndex)); + this.creationInstruction(text.sourceSpan, R3.text, o.literal(nodeIndex)); - this.instruction( - this._bindingCode, text.sourceSpan, R3.textBinding, o.literal(nodeIndex), - this.convertPropertyBinding(o.variable(CONTEXT_NAME), text.value)); + const value = text.value.visit(this._valueConverter); + this.updateInstruction( + text.sourceSpan, R3.textBinding, + () => [o.literal(nodeIndex), this.convertPropertyBinding(o.variable(CONTEXT_NAME), value)]); } visitText(text: t.Text) { - this.instruction( - this._creationCode, text.sourceSpan, R3.text, o.literal(this.allocateDataSlot()), - o.literal(text.value)); + this.creationInstruction( + text.sourceSpan, R3.text, o.literal(this.allocateDataSlot()), o.literal(text.value)); } // When the content of the element is a single text node the translation can be inlined: @@ -736,36 +766,45 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver visitSingleI18nTextChild(text: t.Text, i18nMeta: string) { const meta = parseI18nMeta(i18nMeta); const variable = this.constantPool.getTranslation(text.value, meta); - this.instruction( - this._creationCode, text.sourceSpan, R3.text, o.literal(this.allocateDataSlot()), variable); + this.creationInstruction( + text.sourceSpan, R3.text, o.literal(this.allocateDataSlot()), variable); } private allocateDataSlot() { return this._dataIndex++; } private bindingContext() { return `${this._bindingContext++}`; } private instruction( - statements: o.Statement[], span: ParseSourceSpan|null, reference: o.ExternalReference, - ...params: o.Expression[]) { - statements.push(o.importExpr(reference, null, span).callFn(params, span).toStmt()); + span: ParseSourceSpan|null, reference: o.ExternalReference, + params: o.Expression[]): o.Statement { + return o.importExpr(reference, null, span).callFn(params, span).toStmt(); + } + + private creationInstruction( + span: ParseSourceSpan|null, reference: o.ExternalReference, ...params: o.Expression[]) { + this._creationCode.push(this.instruction(span, reference, params)); + } + + // Bindings must only be resolved after all local refs have been visited, so update mode + // instructions are queued in callbacks that execute once the initial pass has completed. + // Otherwise, we wouldn't be able to support local refs that are defined after their + // bindings. e.g. {{ foo }}
+ private updateInstruction( + span: ParseSourceSpan|null, reference: o.ExternalReference, paramsFn: () => o.Expression[]) { + this._bindingCode.push(() => { return this.instruction(span, reference, paramsFn()); }); } private convertPropertyBinding(implicit: o.Expression, value: AST, skipBindFn?: boolean): o.Expression { - const pipesConvertedValue = value.visit(this._valueConverter); - if (pipesConvertedValue instanceof Interpolation) { - const convertedPropertyBinding = convertPropertyBinding( - this, implicit, pipesConvertedValue, this.bindingContext(), BindingForm.TrySimple, - interpolate); - this._bindingCode.push(...convertedPropertyBinding.stmts); - return convertedPropertyBinding.currValExpr; - } else { - const convertedPropertyBinding = convertPropertyBinding( - this, implicit, pipesConvertedValue, this.bindingContext(), BindingForm.TrySimple, - () => error('Unexpected interpolation')); - this._bindingCode.push(...convertedPropertyBinding.stmts); - const valExpr = convertedPropertyBinding.currValExpr; - return skipBindFn ? valExpr : o.importExpr(R3.bind).callFn([valExpr]); - } + const interpolationFn = + value instanceof Interpolation ? interpolate : () => error('Unexpected interpolation'); + + const convertedPropertyBinding = convertPropertyBinding( + this, implicit, value, this.bindingContext(), BindingForm.TrySimple, interpolationFn); + this._variableCode.push(...convertedPropertyBinding.stmts); + + const valExpr = convertedPropertyBinding.currValExpr; + return value instanceof Interpolation || skipBindFn ? valExpr : + o.importExpr(R3.bind).callFn([valExpr]); } } @@ -897,6 +936,7 @@ export class BindingScope implements LocalResolver { lhs: o.ReadVarExpr; rhs: o.Expression|undefined; declared: boolean; + rhsCallback?: (level: number) => o.Expression; } > (); private referenceNameIndex = 0; @@ -904,7 +944,7 @@ export class BindingScope implements LocalResolver { static ROOT_SCOPE = new BindingScope().set('$event', o.variable('$event')); private constructor( - private parent: BindingScope|null = null, + private level: number = 0, private parent: BindingScope|null = null, private declareLocalVarCallback: DeclareLocalVarCallback = noop) {} get(name: string): o.Expression|null { @@ -914,14 +954,15 @@ export class BindingScope implements LocalResolver { if (value != null) { if (current !== this) { // make a local copy and reset the `declared` state. - value = {lhs: value.lhs, rhs: value.rhs, declared: false}; + value = {lhs: value.lhs, rhs: value.rhs, rhsCallback: value.rhsCallback, declared: false}; // Cache the value locally. this.map.set(name, value); } - if (value.rhs && !value.declared) { + const rhs = value.rhs || value.rhsCallback && value.rhsCallback(this.level); + if (rhs && !value.declared) { // if it is first time we are referencing the variable in the scope - // than invoke the callback to insert variable declaration. - this.declareLocalVarCallback(value.lhs, value.rhs); + // then invoke the callback to insert variable declaration. + this.declareLocalVarCallback(value.lhs, rhs); value.declared = true; } return value.lhs; @@ -940,17 +981,18 @@ export class BindingScope implements LocalResolver { * `undefined` for variable that are ambient such as `$event` and which don't have `rhs` * declaration. */ - set(name: string, lhs: o.ReadVarExpr, rhs?: o.Expression): BindingScope { + set(name: string, lhs: o.ReadVarExpr, rhs?: o.Expression, + rhsCallback?: (level: number) => o.Expression): BindingScope { !this.map.has(name) || error(`The name ${name} is already defined in scope to be ${this.map.get(name)}`); - this.map.set(name, {lhs: lhs, rhs: rhs, declared: false}); + this.map.set(name, {lhs: lhs, rhs: rhs, declared: false, rhsCallback: rhsCallback}); return this; } getLocal(name: string): (o.Expression|null) { return this.get(name); } - nestedScope(declareCallback: DeclareLocalVarCallback): BindingScope { - return new BindingScope(this, declareCallback); + nestedScope(level: number, declareCallback: DeclareLocalVarCallback): BindingScope { + return new BindingScope(level, this, declareCallback); } freshReferenceName(): string { diff --git a/packages/core/src/core_render3_private_export.ts b/packages/core/src/core_render3_private_export.ts index 8107ad315f..4b6fe18f89 100644 --- a/packages/core/src/core_render3_private_export.ts +++ b/packages/core/src/core_render3_private_export.ts @@ -76,6 +76,7 @@ export { e as ɵe, p as ɵp, pD as ɵpD, + r as ɵr, rS as ɵrS, a as ɵa, s as ɵs, diff --git a/packages/core/src/render3/di.ts b/packages/core/src/render3/di.ts index 456417386c..f8f89c49e4 100644 --- a/packages/core/src/render3/di.ts +++ b/packages/core/src/render3/di.ts @@ -24,7 +24,7 @@ import {LInjector} from './interfaces/injector'; import {AttributeMarker, LContainerNode, LElementNode, LNode, LViewNode, TContainerNode, TElementNode, TNodeFlags, TNodeType} from './interfaces/node'; import {LQueries, QueryReadType} from './interfaces/query'; import {Renderer3} from './interfaces/renderer'; -import {DECLARATION_PARENT, DIRECTIVES, HOST_NODE, INJECTOR, LViewData, QUERIES, RENDERER, TVIEW, TView} from './interfaces/view'; +import {DECLARATION_VIEW, DIRECTIVES, HOST_NODE, INJECTOR, LViewData, QUERIES, RENDERER, TVIEW, TView} from './interfaces/view'; import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert'; import {addRemoveViewFromContainer, appendChild, detachView, getChildLNode, getParentLNode, insertView, removeView} from './node_manipulation'; import {ViewRef} from './view_ref'; diff --git a/packages/core/src/render3/index.ts b/packages/core/src/render3/index.ts index 70ba550eb3..69915756d9 100644 --- a/packages/core/src/render3/index.ts +++ b/packages/core/src/render3/index.ts @@ -76,6 +76,8 @@ export { text as T, textBinding as t, + reference as r, + reserveSlots as rS, embeddedViewStart as V, diff --git a/packages/core/src/render3/instructions.ts b/packages/core/src/render3/instructions.ts index ba0c09c8d3..4d48cdf06d 100644 --- a/packages/core/src/render3/instructions.ts +++ b/packages/core/src/render3/instructions.ts @@ -16,13 +16,13 @@ import {assertDefined, assertEqual, assertLessThan, assertNotDefined, assertNotE import {throwCyclicDependencyError, throwErrorIfNoChangesMode, throwMultipleComponentError} from './errors'; import {executeHooks, executeInitHooks, queueInitHooks, queueLifecycleHooks} from './hooks'; import {ACTIVE_INDEX, LContainer, RENDER_PARENT, VIEWS} from './interfaces/container'; -import {ComponentDefInternal, ComponentQuery, ComponentTemplate, DirectiveDefInternal, DirectiveDefListOrFactory, InitialStylingFlags, PipeDefListOrFactory, RenderFlags} from './interfaces/definition'; +import {ComponentDefInternal, ComponentQuery, ComponentTemplate, DirectiveDefInternal, DirectiveDefListOrFactory, EmbeddedTemplate, InitialStylingFlags, PipeDefListOrFactory, RenderFlags} from './interfaces/definition'; import {LInjector} from './interfaces/injector'; import {AttributeMarker, InitialInputData, InitialInputs, LContainerNode, LElementNode, LNode, LProjectionNode, LTextNode, LViewNode, PropertyAliasValue, PropertyAliases, TAttributes, TContainerNode, TElementNode, TNode, TNodeFlags, TNodeType} from './interfaces/node'; import {CssSelectorList, NG_PROJECT_AS_ATTR_NAME} from './interfaces/projection'; import {LQueries} from './interfaces/query'; import {ProceduralRenderer3, RComment, RElement, RText, Renderer3, RendererFactory3, RendererStyleFlags3, isProceduralRenderer} from './interfaces/renderer'; -import {BINDING_INDEX, CLEANUP, CONTAINER_INDEX, CONTENT_QUERIES, CONTEXT, CurrentMatchesList, DECLARATION_PARENT, DIRECTIVES, FLAGS, HEADER_OFFSET, HOST_NODE, INJECTOR, LViewData, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, RootContext, SANITIZER, TAIL, TData, TVIEW, TView} from './interfaces/view'; +import {BINDING_INDEX, CLEANUP, CONTAINER_INDEX, CONTENT_QUERIES, CONTEXT, CurrentMatchesList, DECLARATION_VIEW, DIRECTIVES, FLAGS, HEADER_OFFSET, HOST_NODE, INJECTOR, LViewData, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, RootContext, SANITIZER, TAIL, TData, TVIEW, TView} from './interfaces/view'; import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert'; import {appendChild, appendProjectedNode, canInsertNativeNode, createTextNode, findComponentHost, getChildLNode, getLViewChild, getNextLNode, getParentLNode, insertView, removeView} from './node_manipulation'; import {isNodeMatchingSelectorList, matchingSelectorIndex} from './node_selector_matcher'; @@ -334,7 +334,7 @@ export function createLViewData( null, // tail -1, // containerIndex null, // contentQueries - null // declarationParent + null // declarationView ]; } @@ -500,7 +500,7 @@ export function renderTemplate( * Such lViewNode will then be renderer with renderEmbeddedTemplate() (see below). */ export function createEmbeddedViewNode( - tView: TView, context: T, declarationParent: LViewData, renderer: Renderer3, + tView: TView, context: T, declarationView: LViewData, renderer: Renderer3, queries?: LQueries | null): LViewNode { const _isParent = isParent; const _previousOrParentNode = previousOrParentNode; @@ -509,7 +509,7 @@ export function createEmbeddedViewNode( const lView = createLViewData(renderer, tView, context, LViewFlags.CheckAlways, getCurrentSanitizer()); - lView[DECLARATION_PARENT] = declarationParent; + lView[DECLARATION_VIEW] = declarationView; if (queries) { lView[QUERIES] = queries.createView(); @@ -547,7 +547,7 @@ export function renderEmbeddedTemplate( oldView = enterView(viewNode.data !, viewNode); namespaceHTML(); - callTemplateWithContexts(rf, context, tView.template !, viewNode.data ![DECLARATION_PARENT] !); + callTemplateWithContexts(rf, context, tView.template !, viewNode.data ![DECLARATION_VIEW] !); if (rf & RenderFlags.Update) { refreshDescendantViews(); } else { @@ -577,11 +577,14 @@ export function renderEmbeddedTemplate( *
  • {{ item }}
  • * * - * function AppComponentTemplate(rf, ctx) { - * // instructions - * function ulTemplate(rf, ulCtx, appCtx) {...} - * function liTemplate(rf, liCtx, ulCtx, appCtx) {...} - * } + * function ulTemplate(rf, ulCtx, appCtx) {...} + * function liTemplate(rf, liCtx, ulCtx, appCtx) {...} + * + * class AppComponent {...} + * AppComponent.ngComponentDef = defineComponent({ + * template: function AppComponentTemplate(rf, ctx) {...} + * }); + * * * The ul view's template must be called with its own context and its declaration * parent, AppComponent. The li view's template must be called with its own context, its @@ -591,71 +594,85 @@ export function renderEmbeddedTemplate( * can be declared in different views than they are used. * * @param rf The RenderFlags for this template invocation - * @param context The context for this template + * @param currentContext The context for this template * @param template The template function to call - * @param parent1 The declaration parent of the dynamic view + * @param parentView The declaration view of the dynamic view */ -function callTemplateWithContexts( - rf: RenderFlags, context: any, template: ComponentTemplate, parent1: LViewData): void { - const parent2 = parent1[DECLARATION_PARENT]; +function callTemplateWithContexts( + rf: RenderFlags, currentContext: T, template: EmbeddedTemplate, + parentView: LViewData): void { + const parentContext = parentView[CONTEXT]; + const parentView2 = parentView[DECLARATION_VIEW]; + // Calling a function with extra arguments has a VM cost, so only call with necessary args - if (!parent2) return template(rf, context, parent1[CONTEXT]); - - const parent3 = parent2[DECLARATION_PARENT]; - if (!parent3) return template(rf, context, parent1[CONTEXT], parent2[CONTEXT]); - - const parent4 = parent3[DECLARATION_PARENT]; - if (!parent4) { - return template(rf, context, parent1[CONTEXT], parent2[CONTEXT], parent3[CONTEXT]); + if (parentView2 === null) { + return template(rf, currentContext, parentContext); } - const parent5 = parent4[DECLARATION_PARENT]; - if (!parent5) { - return template( - rf, context, parent1[CONTEXT], parent2[CONTEXT], parent3[CONTEXT], parent4[CONTEXT]); + const parentContext2 = parentView2[CONTEXT]; + const parentView3 = parentView2[DECLARATION_VIEW]; + if (parentView3 === null) { + return template(rf, currentContext, parentContext, parentContext2); } - const parent6 = parent5[DECLARATION_PARENT]; - if (!parent6) { - return template( - rf, context, parent1[CONTEXT], parent2[CONTEXT], parent3[CONTEXT], parent4[CONTEXT], - parent5[CONTEXT]); + const parentContext3 = parentView3[CONTEXT]; + const parentView4 = parentView3[DECLARATION_VIEW]; + if (parentView4 === null) { + return template(rf, currentContext, parentContext, parentContext2, parentContext3); } - const parent7 = parent6[DECLARATION_PARENT]; - if (!parent7) { + const parentContext4 = parentView4[CONTEXT]; + const parentView5 = parentView4[DECLARATION_VIEW]; + if (parentView5 === null) { return template( - rf, context, parent1[CONTEXT], parent2[CONTEXT], parent3[CONTEXT], parent4[CONTEXT], - parent5[CONTEXT], parent6[CONTEXT]); + rf, currentContext, parentContext, parentContext2, parentContext3, parentContext4); } - const parent8 = parent7[DECLARATION_PARENT]; - if (!parent8) { + const parentContext5 = parentView5[CONTEXT]; + const parentView6 = parentView5[DECLARATION_VIEW]; + if (parentView6 === null) { return template( - rf, context, parent1[CONTEXT], parent2[CONTEXT], parent3[CONTEXT], parent4[CONTEXT], - parent5[CONTEXT], parent6[CONTEXT], parent7[CONTEXT]); + rf, currentContext, parentContext, parentContext2, parentContext3, parentContext4, + parentContext5); } - const parent9 = parent8[DECLARATION_PARENT]; - if (!parent9) { + const parentContext6 = parentView6[CONTEXT]; + const parentView7 = parentView6[DECLARATION_VIEW]; + if (parentView7 === null) { return template( - rf, context, parent1[CONTEXT], parent2[CONTEXT], parent3[CONTEXT], parent4[CONTEXT], - parent5[CONTEXT], parent6[CONTEXT], parent7[CONTEXT], parent8[CONTEXT]); + rf, currentContext, parentContext, parentContext2, parentContext3, parentContext4, + parentContext5, parentContext6); + } + + const parentContext7 = parentView7[CONTEXT]; + const parentView8 = parentView7[DECLARATION_VIEW]; + if (parentView8 === null) { + return template( + rf, currentContext, parentContext, parentContext2, parentContext3, parentContext4, + parentContext5, parentContext6, parentContext7); + } + + const parentContext8 = parentView8[CONTEXT]; + const parentView9 = parentView8[DECLARATION_VIEW]; + if (parentView9 === null) { + return template( + rf, currentContext, parentContext, parentContext2, parentContext3, parentContext4, + parentContext5, parentContext6, parentContext7, parentContext8); } // We support up to 8 nesting levels in embedded views before we give up and call apply() - const contexts = [ - parent1[CONTEXT], parent2[CONTEXT], parent3[CONTEXT], parent4[CONTEXT], parent5[CONTEXT], - parent6[CONTEXT], parent7[CONTEXT], parent8[CONTEXT], parent9[CONTEXT] + const templateArgs = [ + rf, currentContext, parentContext, parentContext2, parentContext3, parentContext4, + parentContext5, parentContext6, parentContext7, parentContext8, parentView9[CONTEXT] ]; - let currentView: LViewData = parent9; - while (currentView[DECLARATION_PARENT]) { - contexts.push(currentView[DECLARATION_PARENT] ![CONTEXT]); - currentView = currentView[DECLARATION_PARENT] !; + let currentDeclarationView: LViewData|null = parentView9[DECLARATION_VIEW]; + while (currentDeclarationView) { + templateArgs.push(currentDeclarationView[CONTEXT]); + currentDeclarationView = currentDeclarationView[DECLARATION_VIEW] !; } - tView.template !(rf, context, ...contexts); + template.apply(null, templateArgs); } export function renderComponentOrTemplate( @@ -990,7 +1007,7 @@ function getOrCreateTView( * @param pipes Registry of pipes for this view */ export function createTView( - viewIndex: number, template: ComponentTemplate| null, + viewIndex: number, template: ComponentTemplate| EmbeddedTemplate| null, directives: DirectiveDefListOrFactory | null, pipes: PipeDefListOrFactory | null, viewQuery: ComponentQuery| null): TView { ngDevMode && ngDevMode.tView++; @@ -1811,7 +1828,7 @@ export function createLContainer( * @param localRefs A set of local reference bindings on the element. */ export function container( - index: number, template?: ComponentTemplate, tagName?: string | null, attrs?: TAttributes, + index: number, template?: EmbeddedTemplate, tagName?: string | null, attrs?: TAttributes, localRefs?: string[] | null): void { ngDevMode && assertEqual( @@ -2636,6 +2653,20 @@ export function store(index: number, value: T): void { viewData[adjustedIndex] = value; } +/** Retrieves a value from an LViewData at the given nesting level. */ +export function reference(nestingLevel: number, index: number) { + let currentView = viewData; + while (nestingLevel > 0) { + ngDevMode && assertDefined( + currentView[DECLARATION_VIEW], + 'Declaration view should be defined if nesting level is greater than 0.'); + currentView = currentView[DECLARATION_VIEW] !; + nestingLevel--; + } + + return loadInternal(index, currentView); +} + /** Retrieves a value from the `directives` array. */ export function loadDirective(index: number): T { ngDevMode && assertDefined(directives, 'Directives array should be defined if reading a dir.'); diff --git a/packages/core/src/render3/interfaces/definition.ts b/packages/core/src/render3/interfaces/definition.ts index add5f29cde..111840f9c8 100644 --- a/packages/core/src/render3/interfaces/definition.ts +++ b/packages/core/src/render3/interfaces/definition.ts @@ -11,11 +11,19 @@ import {RendererType2} from '../../render/api'; import {Type} from '../../type'; import {CssSelectorList} from './projection'; + /** - * Definition of what a template rendering function should look like. + * Definition of what a template rendering function should look like for a component. */ export type ComponentTemplate = { - (rf: RenderFlags, ctx: T, ...parentCtx: ({} | null)[]): void; ngPrivateData?: never; + (rf: RenderFlags, ctx: T): void; ngPrivateData?: never; +}; + +/** + * Definition of what a template rendering function should look like for an embedded view. + */ +export type EmbeddedTemplate = { + (rf: RenderFlags, ctx: T, ...parentCtx: any[]): void; }; /** diff --git a/packages/core/src/render3/interfaces/view.ts b/packages/core/src/render3/interfaces/view.ts index a55e6ba734..168bbabec3 100644 --- a/packages/core/src/render3/interfaces/view.ts +++ b/packages/core/src/render3/interfaces/view.ts @@ -38,7 +38,7 @@ export const SANITIZER = 12; export const TAIL = 13; export const CONTAINER_INDEX = 14; export const CONTENT_QUERIES = 15; -export const DECLARATION_PARENT = 16; +export const DECLARATION_VIEW = 16; /** * `LViewData` stores all of the information needed to process the instructions as @@ -63,7 +63,7 @@ export interface LViewData extends Array { * `LViewData`. Without this, the render method would have to keep a stack of * views as it is recursively rendering templates. * - * This is also the "insertion" parent for embedded views. This allows us to properly + * This is the "insertion" view for embedded views. This allows us to properly * destroy embedded views. */ [PARENT]: LViewData|null; @@ -167,16 +167,16 @@ export interface LViewData extends Array { [CONTENT_QUERIES]: QueryList[]|null; /** - * Parent view where this view's template was declared. + * View where this view's template was declared. * * Only applicable for dynamically created views. Will be null for inline/component views. * * The template for a dynamically created view may be declared in a different view than - * it is inserted. We already track the "insertion parent" (view where the template was - * inserted) in LViewData[PARENT], but we also need access to the "declaration parent" + * it is inserted. We already track the "insertion view" (view where the template was + * inserted) in LViewData[PARENT], but we also need access to the "declaration view" * (view where the template was declared). Otherwise, we wouldn't be able to call the * view's template function with the proper contexts. Context should be inherited from - * the declaration parent tree, not the insertion parent tree. + * the declaration view tree, not the insertion view tree. * * Example (AppComponent template): * @@ -184,13 +184,13 @@ export interface LViewData extends Array { * <-- inserted inside this component --> * * The above is declared in the AppComponent template, but it will be passed into - * SomeComp and inserted there. In this case, the declaration parent would be the AppComponent, - * but the insertion parent would be SomeComp. When we are removing views, we would want to - * traverse through the insertion parent to clean up listeners. When we are calling the - * template function during change detection, we need the declaration parent to get inherited + * SomeComp and inserted there. In this case, the declaration view would be the AppComponent, + * but the insertion view would be SomeComp. When we are removing views, we would want to + * traverse through the insertion view to clean up listeners. When we are calling the + * template function during change detection, we need the declaration view to get inherited * context. */ - [DECLARATION_PARENT]: LViewData|null; + [DECLARATION_VIEW]: LViewData|null; } /** Flags associated with an LView (saved in LViewData[FLAGS]) */ diff --git a/packages/core/src/render3/jit/environment.ts b/packages/core/src/render3/jit/environment.ts index 9a2137d1d8..24ae08319d 100644 --- a/packages/core/src/render3/jit/environment.ts +++ b/packages/core/src/render3/jit/environment.ts @@ -81,6 +81,7 @@ export const angularCoreEnv: {[name: string]: Function} = { 'ɵqR': r3.qR, 'ɵQr': r3.Qr, 'ɵrS': r3.rS, + 'ɵr': r3.r, 'ɵs': r3.s, 'ɵsm': r3.sm, 'ɵsp': r3.sp, diff --git a/packages/core/src/render3/view_ref.ts b/packages/core/src/render3/view_ref.ts index 1b23cf216c..b812568247 100644 --- a/packages/core/src/render3/view_ref.ts +++ b/packages/core/src/render3/view_ref.ts @@ -12,6 +12,7 @@ import {ViewContainerRef as viewEngine_ViewContainerRef} from '../linker/view_co import {EmbeddedViewRef as viewEngine_EmbeddedViewRef, InternalViewRef as viewEngine_InternalViewRef} from '../linker/view_ref'; import {checkNoChanges, detectChanges, markViewDirty, storeCleanupFn, viewAttached} from './instructions'; +import {EmbeddedTemplate} from './interfaces/definition'; import {LViewNode} from './interfaces/node'; import {FLAGS, LViewData, LViewFlags} from './interfaces/view'; import {destroyLView} from './node_manipulation'; @@ -243,3 +244,30 @@ export class ViewRef implements viewEngine_EmbeddedViewRef, viewEngine_Int attachToAppRef(appRef: ApplicationRef) { this._appRef = appRef; } } +<<<<<<< HEAD +======= + + +export class EmbeddedViewRef extends ViewRef { + /** + * @internal + */ + _lViewNode: LViewNode; + private _viewContainerRef: viewEngine_ViewContainerRef|null = null; + + constructor(viewNode: LViewNode, template: EmbeddedTemplate, context: T) { + super(viewNode.data, context); + this._lViewNode = viewNode; + } + + destroy(): void { + if (this._viewContainerRef && viewAttached(this._view)) { + this._viewContainerRef.detach(this._viewContainerRef.indexOf(this)); + this._viewContainerRef = null; + } + super.destroy(); + } + + attachToViewContainerRef(vcRef: viewEngine_ViewContainerRef) { this._viewContainerRef = vcRef; } +} +>>>>>>> fixup! fix(ivy): flatten template fns for nested views diff --git a/packages/core/test/bundling/hello_world/bundle.golden_symbols.json b/packages/core/test/bundling/hello_world/bundle.golden_symbols.json index 68ee9b8b51..fabcd26ce5 100644 --- a/packages/core/test/bundling/hello_world/bundle.golden_symbols.json +++ b/packages/core/test/bundling/hello_world/bundle.golden_symbols.json @@ -17,6 +17,9 @@ { "name": "ChangeDetectionStrategy" }, + { + "name": "DECLARATION_VIEW" + }, { "name": "DIRECTIVES" }, @@ -98,6 +101,9 @@ { "name": "callHooks" }, + { + "name": "callTemplateWithContexts" + }, { "name": "canInsertNativeNode" }, @@ -210,10 +216,10 @@ "name": "refreshContentQueries" }, { - "name": "refreshDynamicEmbeddedViews" + "name": "refreshDescendantViews" }, { - "name": "refreshView" + "name": "refreshDynamicEmbeddedViews" }, { "name": "renderComponent" diff --git a/packages/core/test/bundling/todo/bundle.golden_symbols.json b/packages/core/test/bundling/todo/bundle.golden_symbols.json index c7a1f9e57f..3cf638fdc6 100644 --- a/packages/core/test/bundling/todo/bundle.golden_symbols.json +++ b/packages/core/test/bundling/todo/bundle.golden_symbols.json @@ -26,6 +26,9 @@ { "name": "ChangeDetectionStrategy" }, + { + "name": "DECLARATION_VIEW" + }, { "name": "DIRECTIVES" }, @@ -137,6 +140,24 @@ { "name": "TemplateRef$1" }, + { + "name": "ToDoAppComponent_footer_Template_6" + }, + { + "name": "ToDoAppComponent_footer_button_Template_5" + }, + { + "name": "ToDoAppComponent_section_Template_5" + }, + { + "name": "ToDoAppComponent_section_input_Template_1" + }, + { + "name": "ToDoAppComponent_section_li_Template_3" + }, + { + "name": "ToDoAppComponent_section_li_input_Template_6" + }, { "name": "Todo" }, @@ -305,6 +326,9 @@ { "name": "callHooks" }, + { + "name": "callTemplateWithContexts" + }, { "name": "canInsertNativeNode" }, @@ -701,6 +725,9 @@ { "name": "readElementValue" }, + { + "name": "reference" + }, { "name": "refreshChildComponents" }, @@ -708,10 +735,10 @@ "name": "refreshContentQueries" }, { - "name": "refreshDynamicEmbeddedViews" + "name": "refreshDescendantViews" }, { - "name": "refreshView" + "name": "refreshDynamicEmbeddedViews" }, { "name": "removeListeners" diff --git a/packages/core/test/render3/common_integration_spec.ts b/packages/core/test/render3/common_integration_spec.ts index 49c3705e60..0e7a49d728 100644 --- a/packages/core/test/render3/common_integration_spec.ts +++ b/packages/core/test/render3/common_integration_spec.ts @@ -226,34 +226,35 @@ describe('@angular/common integration', () => { elementProperty(1, 'ngForOf', bind(myApp.items)); } - function liTemplate(rf1: RenderFlags, row: any, myApp: MyApp) { - if (rf1 & RenderFlags.Create) { - elementStart(0, 'li'); - { container(1, spanTemplate, null, ['ngForOf', '']); } - elementEnd(); - } - if (rf1 & RenderFlags.Update) { - const r1 = row.$implicit as any; - elementProperty(1, 'ngForOf', bind(r1.data)); - } - } - - function spanTemplate(rf1: RenderFlags, cell: any, row: any, myApp: MyApp) { - if (rf1 & RenderFlags.Create) { - elementStart(0, 'span'); - { text(1); } - elementEnd(); - } - if (rf1 & RenderFlags.Update) { - textBinding( - 1, interpolation2('', cell.$implicit, ' - ', (row.$implicit as any).value, '')); - } - } }, directives: () => [NgForOf] }); } + function liTemplate(rf1: RenderFlags, row: any, myApp: MyApp) { + if (rf1 & RenderFlags.Create) { + elementStart(0, 'li'); + { container(1, spanTemplate, null, ['ngForOf', '']); } + elementEnd(); + } + if (rf1 & RenderFlags.Update) { + const r1 = row.$implicit as any; + elementProperty(1, 'ngForOf', bind(r1.data)); + } + } + + function spanTemplate(rf1: RenderFlags, cell: any, row: any, myApp: MyApp) { + if (rf1 & RenderFlags.Create) { + elementStart(0, 'span'); + { text(1); } + elementEnd(); + } + if (rf1 & RenderFlags.Update) { + textBinding( + 1, interpolation2('', cell.$implicit, ' - ', (row.$implicit as any).value, '')); + } + } + const fixture = new ComponentFixture(MyApp); // Change detection cycle, no model changes @@ -392,137 +393,136 @@ describe('@angular/common integration', () => { elementProperty(0, 'ngForOf', bind(myApp.items)); } - function itemTemplate0(rf1: RenderFlags, item0: any, myApp: MyApp) { - if (rf1 & RenderFlags.Create) { - elementStart(0, 'span'); - { container(1, itemTemplate1, null, ['ngForOf', '']); } - elementEnd(); - } - if (rf1 & RenderFlags.Update) { - const item = item0.$implicit as any; - elementProperty(1, 'ngForOf', bind(item.data)); - } - } - - function itemTemplate1(rf1: RenderFlags, item1: any, item0: any, myApp: MyApp) { - if (rf1 & RenderFlags.Create) { - elementStart(0, 'span'); - { container(1, itemTemplate2, null, ['ngForOf', '']); } - elementEnd(); - } - if (rf1 & RenderFlags.Update) { - const item = item1.$implicit as any; - elementProperty(1, 'ngForOf', bind(item.data)); - } - } - - function itemTemplate2( - rf1: RenderFlags, item2: any, item1: any, item0: any, myApp: MyApp) { - if (rf1 & RenderFlags.Create) { - elementStart(0, 'span'); - { container(1, itemTemplate3, null, ['ngForOf', '']); } - elementEnd(); - } - if (rf1 & RenderFlags.Update) { - const item = item2.$implicit as any; - elementProperty(1, 'ngForOf', bind(item.data)); - } - } - - function itemTemplate3( - rf1: RenderFlags, item3: any, item2: any, item1: any, item0: any, myApp: MyApp) { - if (rf1 & RenderFlags.Create) { - elementStart(0, 'span'); - { container(1, itemTemplate4, null, ['ngForOf', '']); } - elementEnd(); - } - if (rf1 & RenderFlags.Update) { - const item = item3.$implicit as any; - elementProperty(1, 'ngForOf', bind(item.data)); - } - } - - function itemTemplate4( - rf1: RenderFlags, item4: any, item3: any, item2: any, item1: any, item0: any, - myApp: MyApp) { - if (rf1 & RenderFlags.Create) { - elementStart(0, 'span'); - { container(1, itemTemplate5, null, ['ngForOf', '']); } - elementEnd(); - } - if (rf1 & RenderFlags.Update) { - const item = item4.$implicit as any; - elementProperty(1, 'ngForOf', bind(item.data)); - } - } - - function itemTemplate5( - rf1: RenderFlags, item5: any, item4: any, item3: any, item2: any, item1: any, - item0: any, myApp: MyApp) { - if (rf1 & RenderFlags.Create) { - elementStart(0, 'span'); - { container(1, itemTemplate6, null, ['ngForOf', '']); } - elementEnd(); - } - if (rf1 & RenderFlags.Update) { - const item = item5.$implicit as any; - elementProperty(1, 'ngForOf', bind(item.data)); - } - } - - function itemTemplate6( - rf1: RenderFlags, item6: any, item5: any, item4: any, item3: any, item2: any, - item1: any, item0: any, myApp: MyApp) { - if (rf1 & RenderFlags.Create) { - elementStart(0, 'span'); - { container(1, itemTemplate7, null, ['ngForOf', '']); } - elementEnd(); - } - if (rf1 & RenderFlags.Update) { - const item = item6.$implicit as any; - elementProperty(1, 'ngForOf', bind(item.data)); - } - } - - function itemTemplate7( - rf1: RenderFlags, item7: any, item6: any, item5: any, item4: any, item3: any, - item2: any, item1: any, item0: any, myApp: MyApp) { - if (rf1 & RenderFlags.Create) { - elementStart(0, 'span'); - { container(1, itemTemplate8, null, ['ngForOf', '']); } - elementEnd(); - } - if (rf1 & RenderFlags.Update) { - const item = item7.$implicit as any; - elementProperty(1, 'ngForOf', bind(item.data)); - } - } - - function itemTemplate8( - rf1: RenderFlags, item8: any, item7: any, item6: any, item5: any, item4: any, - item3: any, item2: any, item1: any, item0: any, myApp: MyApp) { - if (rf1 & RenderFlags.Create) { - elementStart(0, 'span'); - { text(1); } - elementEnd(); - } - - if (rf1 & RenderFlags.Update) { - textBinding(1, interpolationV([ - '', item8.$implicit, '.', item7.$implicit.value, - '.', item6.$implicit.value, '.', item5.$implicit.value, - '.', item4.$implicit.value, '.', item3.$implicit.value, - '.', item2.$implicit.value, '.', item1.$implicit.value, - '.', item0.$implicit.value, '.', myApp.value, - '' - ])); - } - } }, directives: () => [NgForOf] }); } + function itemTemplate0(rf1: RenderFlags, item0: any, myApp: MyApp) { + if (rf1 & RenderFlags.Create) { + elementStart(0, 'span'); + { container(1, itemTemplate1, null, ['ngForOf', '']); } + elementEnd(); + } + if (rf1 & RenderFlags.Update) { + const item = item0.$implicit as any; + elementProperty(1, 'ngForOf', bind(item.data)); + } + } + + function itemTemplate1(rf1: RenderFlags, item1: any, item0: any, myApp: MyApp) { + if (rf1 & RenderFlags.Create) { + elementStart(0, 'span'); + { container(1, itemTemplate2, null, ['ngForOf', '']); } + elementEnd(); + } + if (rf1 & RenderFlags.Update) { + const item = item1.$implicit as any; + elementProperty(1, 'ngForOf', bind(item.data)); + } + } + + function itemTemplate2(rf1: RenderFlags, item2: any, item1: any, item0: any, myApp: MyApp) { + if (rf1 & RenderFlags.Create) { + elementStart(0, 'span'); + { container(1, itemTemplate3, null, ['ngForOf', '']); } + elementEnd(); + } + if (rf1 & RenderFlags.Update) { + const item = item2.$implicit as any; + elementProperty(1, 'ngForOf', bind(item.data)); + } + } + + function itemTemplate3( + rf1: RenderFlags, item3: any, item2: any, item1: any, item0: any, myApp: MyApp) { + if (rf1 & RenderFlags.Create) { + elementStart(0, 'span'); + { container(1, itemTemplate4, null, ['ngForOf', '']); } + elementEnd(); + } + if (rf1 & RenderFlags.Update) { + const item = item3.$implicit as any; + elementProperty(1, 'ngForOf', bind(item.data)); + } + } + + function itemTemplate4( + rf1: RenderFlags, item4: any, item3: any, item2: any, item1: any, item0: any, + myApp: MyApp) { + if (rf1 & RenderFlags.Create) { + elementStart(0, 'span'); + { container(1, itemTemplate5, null, ['ngForOf', '']); } + elementEnd(); + } + if (rf1 & RenderFlags.Update) { + const item = item4.$implicit as any; + elementProperty(1, 'ngForOf', bind(item.data)); + } + } + + function itemTemplate5( + rf1: RenderFlags, item5: any, item4: any, item3: any, item2: any, item1: any, item0: any, + myApp: MyApp) { + if (rf1 & RenderFlags.Create) { + elementStart(0, 'span'); + { container(1, itemTemplate6, null, ['ngForOf', '']); } + elementEnd(); + } + if (rf1 & RenderFlags.Update) { + const item = item5.$implicit as any; + elementProperty(1, 'ngForOf', bind(item.data)); + } + } + + function itemTemplate6( + rf1: RenderFlags, item6: any, item5: any, item4: any, item3: any, item2: any, item1: any, + item0: any, myApp: MyApp) { + if (rf1 & RenderFlags.Create) { + elementStart(0, 'span'); + { container(1, itemTemplate7, null, ['ngForOf', '']); } + elementEnd(); + } + if (rf1 & RenderFlags.Update) { + const item = item6.$implicit as any; + elementProperty(1, 'ngForOf', bind(item.data)); + } + } + + function itemTemplate7( + rf1: RenderFlags, item7: any, item6: any, item5: any, item4: any, item3: any, item2: any, + item1: any, item0: any, myApp: MyApp) { + if (rf1 & RenderFlags.Create) { + elementStart(0, 'span'); + { container(1, itemTemplate8, null, ['ngForOf', '']); } + elementEnd(); + } + if (rf1 & RenderFlags.Update) { + const item = item7.$implicit as any; + elementProperty(1, 'ngForOf', bind(item.data)); + } + } + + function itemTemplate8( + rf1: RenderFlags, item8: any, item7: any, item6: any, item5: any, item4: any, item3: any, + item2: any, item1: any, item0: any, myApp: MyApp) { + if (rf1 & RenderFlags.Create) { + elementStart(0, 'span'); + { text(1); } + elementEnd(); + } + + if (rf1 & RenderFlags.Update) { + textBinding( + 1, interpolationV([ + '', item8.$implicit, '.', item7.$implicit.value, '.', item6.$implicit.value, + '.', item5.$implicit.value, '.', item4.$implicit.value, '.', item3.$implicit.value, + '.', item2.$implicit.value, '.', item1.$implicit.value, '.', item0.$implicit.value, + '.', myApp.value, '' + ])); + } + } + const fixture = new ComponentFixture(MyApp); expect(fixture.html) @@ -564,31 +564,33 @@ describe('@angular/common integration', () => { elementProperty(1, 'ngIf', bind(myApp.showing)); } - function templateOne(rf: RenderFlags, ctx: any, parent: MyApp) { - if (rf & RenderFlags.Create) { - elementStart(0, 'div'); - { text(1); } - elementEnd(); - } - if (rf & RenderFlags.Update) { - textBinding(1, bind(myApp.valueOne)); - } - } - function templateTwo(rf: RenderFlags, ctx: any, parent: MyApp) { - if (rf & RenderFlags.Create) { - elementStart(0, 'div'); - { text(1); } - elementEnd(); - } - if (rf & RenderFlags.Update) { - textBinding(1, bind(myApp.valueTwo)); - } - } }, directives: () => [NgIf] }); } + function templateOne(rf: RenderFlags, ctx: any, myApp: MyApp) { + if (rf & RenderFlags.Create) { + elementStart(0, 'div'); + { text(1); } + elementEnd(); + } + if (rf & RenderFlags.Update) { + textBinding(1, bind(myApp.valueOne)); + } + } + + function templateTwo(rf: RenderFlags, ctx: any, myApp: MyApp) { + if (rf & RenderFlags.Create) { + elementStart(0, 'div'); + { text(1); } + elementEnd(); + } + if (rf & RenderFlags.Update) { + textBinding(1, bind(myApp.valueTwo)); + } + } + const fixture = new ComponentFixture(MyApp); expect(fixture.html).toEqual('
    one
    two
    '); diff --git a/packages/core/test/render3/component_spec.ts b/packages/core/test/render3/component_spec.ts index 5fe7882474..9ff8e5b3c6 100644 --- a/packages/core/test/render3/component_spec.ts +++ b/packages/core/test/render3/component_spec.ts @@ -389,28 +389,31 @@ describe('recursive components', () => { elementProperty(2, 'ngIf', bind(ctx.data.right)); } - function IfTemplate(rf1: RenderFlags, left: any, parent: NgIfTree) { - if (rf1 & RenderFlags.Create) { - elementStart(0, 'ng-if-tree'); - elementEnd(); - } - if (rf1 & RenderFlags.Update) { - elementProperty(0, 'data', bind(parent.data.left)); - } - } - function IfTemplate2(rf1: RenderFlags, right: any, parent: NgIfTree) { - if (rf1 & RenderFlags.Create) { - elementStart(0, 'ng-if-tree'); - elementEnd(); - } - if (rf1 & RenderFlags.Update) { - elementProperty(0, 'data', bind(parent.data.right)); - } - } }, inputs: {data: 'data'}, }); } + + function IfTemplate(rf1: RenderFlags, left: any, parent: NgIfTree) { + if (rf1 & RenderFlags.Create) { + elementStart(0, 'ng-if-tree'); + elementEnd(); + } + if (rf1 & RenderFlags.Update) { + elementProperty(0, 'data', bind(parent.data.left)); + } + } + + function IfTemplate2(rf1: RenderFlags, right: any, parent: NgIfTree) { + if (rf1 & RenderFlags.Create) { + elementStart(0, 'ng-if-tree'); + elementEnd(); + } + if (rf1 & RenderFlags.Update) { + elementProperty(0, 'data', bind(parent.data.right)); + } + } + (NgIfTree.ngComponentDef as ComponentDefInternal).directiveDefs = () => [NgIfTree.ngComponentDef, NgIf.ngDirectiveDef]; diff --git a/packages/core/test/render3/content_spec.ts b/packages/core/test/render3/content_spec.ts index 91eb94c1ec..c4ba46cc4a 100644 --- a/packages/core/test/render3/content_spec.ts +++ b/packages/core/test/render3/content_spec.ts @@ -823,14 +823,15 @@ describe('content projection', () => { elementProperty(1, 'ngIf', bind(ctx.showing)); } - function IfTemplate(rf1: RenderFlags, ctx1: any, child: any) { - if (rf1 & RenderFlags.Create) { - projectionDef(); - projection(0); - } - } }, [NgIf]); + function IfTemplate(rf1: RenderFlags, ctx: any, child: any) { + if (rf1 & RenderFlags.Create) { + projectionDef(); + projection(0); + } + } + let child: {showing: boolean}; /** * @@ -887,14 +888,15 @@ describe('content projection', () => { elementProperty(1, 'ngIf', bind(ctx.showing)); } - function IfTemplate(rf1: RenderFlags, ctx1: any, child: any) { - if (rf1 & RenderFlags.Create) { - projectionDef(); - projection(0); - } - } }, [NgIf]); + function IfTemplate(rf: RenderFlags, ctx: any, child: any) { + if (rf & RenderFlags.Create) { + projectionDef(); + projection(0); + } + } + let child: {showing: boolean}; /** * diff --git a/packages/core/test/render3/exports_spec.ts b/packages/core/test/render3/exports_spec.ts index 3578a49e97..4a34e35897 100644 --- a/packages/core/test/render3/exports_spec.ts +++ b/packages/core/test/render3/exports_spec.ts @@ -6,10 +6,11 @@ * found in the LICENSE file at https://angular.io/license */ -import {defineComponent, defineDirective} from '../../src/render3/index'; -import {bind, container, containerRefreshEnd, containerRefreshStart, element, elementAttribute, elementClassProp, elementEnd, elementProperty, elementStart, elementStyling, elementStylingApply, embeddedViewEnd, embeddedViewStart, load, text, textBinding} from '../../src/render3/instructions'; +import {AttributeMarker, defineComponent, defineDirective} from '../../src/render3/index'; +import {bind, container, containerRefreshEnd, containerRefreshStart, element, elementAttribute, elementClassProp, elementEnd, elementProperty, elementStart, elementStyling, elementStylingApply, embeddedViewEnd, embeddedViewStart, interpolation2, load, reference, text, textBinding} from '../../src/render3/instructions'; import {InitialStylingFlags, RenderFlags} from '../../src/render3/interfaces/definition'; +import {NgIf} from './common_with_def'; import {ComponentFixture, createComponent, renderToHtml} from './render_util'; describe('exports', () => { @@ -21,9 +22,8 @@ describe('exports', () => { element(0, 'input', ['value', 'one'], ['myInput', '']); text(2); } - let tmp: any; if (rf & RenderFlags.Update) { - tmp = load(1); + const tmp = load(1) as any; textBinding(2, tmp.value); } } @@ -39,9 +39,8 @@ describe('exports', () => { element(0, 'comp', null, ['myComp', '']); text(2); } - let tmp: any; if (rf & RenderFlags.Update) { - tmp = load(1); + const tmp = load(1) as any; textBinding(2, tmp.name); } } @@ -94,9 +93,8 @@ describe('exports', () => { element(0, 'comp', null, ['myComp', '']); element(2, 'div', ['myDir', '']); } - let tmp: any; if (rf & RenderFlags.Update) { - tmp = load(1); + const tmp = load(1) as any; elementProperty(2, 'myDir', bind(tmp)); } } @@ -113,9 +111,8 @@ describe('exports', () => { element(0, 'div', ['someDir', ''], ['myDir', 'someDir']); text(2); } - let tmp: any; if (rf & RenderFlags.Update) { - tmp = load(1); + const tmp = load(1) as any; textBinding(2, tmp.name); } } @@ -155,8 +152,8 @@ describe('exports', () => { text(0); element(1, 'input', ['value', 'one'], ['myInput', '']); } - const tmp = load(2) as any; if (rf & RenderFlags.Update) { + const tmp = load(2) as any; textBinding(0, bind(tmp.value)); } } @@ -172,8 +169,8 @@ describe('exports', () => { element(0, 'div'); element(1, 'input', ['value', 'one'], ['myInput', '']); } - const tmp = load(2) as any; if (rf & RenderFlags.Update) { + const tmp = load(2) as any; elementProperty(0, 'title', bind(tmp.value)); } } @@ -188,8 +185,8 @@ describe('exports', () => { element(0, 'div'); element(1, 'input', ['value', 'one'], ['myInput', '']); } - const tmp = load(2) as any; if (rf & RenderFlags.Update) { + const tmp = load(2) as any; elementAttribute(0, 'aria-label', bind(tmp.value)); } } @@ -206,8 +203,8 @@ describe('exports', () => { elementEnd(); element(1, 'input', ['type', 'checkbox', 'checked', 'true'], ['myInput', '']); } - const tmp = load(2) as any; if (rf & RenderFlags.Update) { + const tmp = load(2) as any; elementClassProp(0, 0, tmp.checked); elementStylingApply(0); } @@ -253,9 +250,8 @@ describe('exports', () => { element(0, 'div', ['myDir', '']); element(1, 'comp', null, ['myComp', '']); } - let tmp: any; if (rf & RenderFlags.Update) { - tmp = load(2) as any; + const tmp = load(2) as any; elementProperty(0, 'myDir', bind(tmp)); } } @@ -274,11 +270,9 @@ describe('exports', () => { element(2, 'comp', null, ['myComp', '']); element(4, 'input', ['value', 'one'], ['myInput', '']); } - let tmp1: any; - let tmp2: any; if (rf & RenderFlags.Update) { - tmp1 = load(3) as any; - tmp2 = load(5) as any; + const tmp1 = load(3) as any; + const tmp2 = load(5) as any; textBinding(0, bind(tmp2.value)); textBinding(1, bind(tmp1.name)); } @@ -315,13 +309,12 @@ describe('exports', () => { if (ctx.condition) { let rf1 = embeddedViewStart(1); { - let tmp: any; if (rf1 & RenderFlags.Create) { text(0); element(1, 'input', ['value', 'one'], ['myInput', '']); } if (rf1 & RenderFlags.Update) { - tmp = load(2); + const tmp = load(2) as any; textBinding(0, bind(tmp.value)); } } @@ -337,5 +330,71 @@ describe('exports', () => { })).toEqual('
    one
    '); expect(renderToHtml(Template, {condition: false})).toEqual('
    '); }); + + it('should support local refs in nested dynamic views', () => { + /** + * + *
    + * {{ outerInput.value }} + * + * + * + *
    + * {{ outerInput.value }} - {{ innerInput.value}} + *
    + *
    + */ + const App = createComponent('app', function(rf: RenderFlags, app: any) { + if (rf & RenderFlags.Create) { + elementStart(0, 'input', ['value', 'one'], ['outerInput', '']); + elementEnd(); + container(2, outerTemplate, '', [AttributeMarker.SelectOnly, 'ngIf']); + } + if (rf & RenderFlags.Update) { + elementProperty(2, 'ngIf', bind(app.outer)); + } + }, [NgIf]); + + function outerTemplate(rf: RenderFlags, outer: any, app: any) { + if (rf & RenderFlags.Create) { + elementStart(0, 'div'); + { + text(1); + elementStart(2, 'input', ['value', 'two'], ['innerInput', '']); + elementEnd(); + container(4, innerTemplate, '', [AttributeMarker.SelectOnly, 'ngIf']); + } + elementEnd(); + } + + const outerInput = reference(1, 1) as any; + if (rf & RenderFlags.Update) { + textBinding(1, bind(outerInput.value)); + elementProperty(4, 'ngIf', bind(app.inner)); + } + } + + function innerTemplate(rf: RenderFlags, inner: any, outer: any, app: any) { + if (rf & RenderFlags.Create) { + elementStart(0, 'div'); + { text(1); } + elementEnd(); + } + + const outerInput = reference(2, 1) as any; + const innerInput = reference(1, 3) as any; + if (rf & RenderFlags.Update) { + textBinding(1, interpolation2('', outerInput.value, ' - ', innerInput.value, '')); + } + } + + const fixture = new ComponentFixture(App); + fixture.component.outer = true; + fixture.component.inner = true; + fixture.update(); + expect(fixture.html) + .toEqual(`
    one
    one - two
    `); + }); + }); }); diff --git a/packages/core/test/render3/view_container_ref_spec.ts b/packages/core/test/render3/view_container_ref_spec.ts index bf37e84852..b1d404b3da 100644 --- a/packages/core/test/render3/view_container_ref_spec.ts +++ b/packages/core/test/render3/view_container_ref_spec.ts @@ -506,9 +506,9 @@ describe('ViewContainerRef', () => { * * <-- template insertion inside */ - const Parent = createComponent('parent', function(rf: RenderFlags, ctx: any) { + const Parent = createComponent('parent', function(rf: RenderFlags, parent: any) { if (rf & RenderFlags.Create) { - container(0, template); + container(0, fooTemplate); elementStart(1, 'child'); elementEnd(); } @@ -519,19 +519,20 @@ describe('ViewContainerRef', () => { elementProperty(1, 'tpl', bind(tplRef)); } - function template(rf1: RenderFlags, ctx1: any, parent: any) { - if (rf1 & RenderFlags.Create) { - elementStart(0, 'div'); - { text(1); } - elementEnd(); - } - - if (rf1 & RenderFlags.Update) { - textBinding(1, bind(parent.name)); - } - } }, [Child]); + function fooTemplate(rf1: RenderFlags, ctx: any, parent: any) { + if (rf1 & RenderFlags.Create) { + elementStart(0, 'div'); + { text(1); } + elementEnd(); + } + + if (rf1 & RenderFlags.Update) { + textBinding(1, bind(parent.name)); + } + } + const fixture = new ComponentFixture(Parent); fixture.component.name = 'Parent'; fixture.update(); @@ -609,37 +610,37 @@ describe('ViewContainerRef', () => { elementProperty(1, 'rows', bind(parent.rows)); } - function rowTemplate(rf1: RenderFlags, row: any, parent: any) { - if (rf1 & RenderFlags.Create) { - container(0, cellTemplate); - elementStart(1, 'loop-comp'); - elementEnd(); - } - - if (rf1 & RenderFlags.Update) { - // Hack until we have local refs for templates - const cellTemplateRef = - getOrCreateTemplateRef(getOrCreateNodeInjectorForNode(load(0))); - elementProperty(1, 'tpl', bind(cellTemplateRef)); - elementProperty(1, 'rows', bind(row.$implicit.data)); - } - } - - function cellTemplate(rf1: RenderFlags, cell: any, row: any, parent: any) { - if (rf1 & RenderFlags.Create) { - elementStart(0, 'div'); - { text(1); } - elementEnd(); - } - - if (rf1 & RenderFlags.Update) { - textBinding( - 1, interpolation3( - '', cell.$implicit, ' - ', row.$implicit.value, ' - ', parent.name, '')); - } - } }, [LoopComp]); + function rowTemplate(rf1: RenderFlags, row: any, parent: any) { + if (rf1 & RenderFlags.Create) { + container(0, cellTemplate); + elementStart(1, 'loop-comp'); + elementEnd(); + } + + if (rf1 & RenderFlags.Update) { + // Hack until we have local refs for templates + const cellTemplateRef = getOrCreateTemplateRef(getOrCreateNodeInjectorForNode(load(0))); + elementProperty(1, 'tpl', bind(cellTemplateRef)); + elementProperty(1, 'rows', bind(row.$implicit.data)); + } + } + + function cellTemplate(rf1: RenderFlags, cell: any, row: any, parent: any) { + if (rf1 & RenderFlags.Create) { + elementStart(0, 'div'); + { text(1); } + elementEnd(); + } + + if (rf1 & RenderFlags.Update) { + textBinding( + 1, interpolation3( + '', cell.$implicit, ' - ', row.$implicit.value, ' - ', parent.name, '')); + } + } + const fixture = new ComponentFixture(Parent); fixture.component.name = 'Parent'; fixture.component.rows =