diff --git a/packages/compiler-cli/test/compliance/README.md b/packages/compiler-cli/test/compliance/README.md index 7c522a1396..42e76de5db 100644 --- a/packages/compiler-cli/test/compliance/README.md +++ b/packages/compiler-cli/test/compliance/README.md @@ -1 +1,3 @@ -Tests in this directory are excluded from running in the browser and only run in node. \ No newline at end of file +Tests in this directory should be run with: + +bazel test --define=compile=local packages/compiler-cli/test/compliance:compliance 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 424ea3c0b9..3540e9700a 100644 --- a/packages/compiler-cli/test/compliance/r3_compiler_compliance_spec.ts +++ b/packages/compiler-cli/test/compliance/r3_compiler_compliance_spec.ts @@ -541,15 +541,16 @@ describe('compiler compliance', () => { const MyComponentDefinition = ` const $c1$ = ["foo", ""]; const $c2$ = ["if", ""]; - function MyComponent_li_Template_2(rf, ctx0, ctx) { + function MyComponent_li_Template_2(rf, 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$, "")); + const $myComp$ = $r3$.ɵx(); + const $foo$ = $r3$.ɵr(1); + $r3$.ɵt(1, $r3$.ɵi2("", $myComp$.salutation, " ", $foo$, "")); } } … @@ -1174,7 +1175,7 @@ describe('compiler compliance', () => { $r3$.ɵT(2); } if (rf & 2) { - const $user$ = $r3$.ɵld(1); + const $user$ = $r3$.ɵr(1); $r3$.ɵt(2, $r3$.ɵi1("Hello ", $user$.value, "!")); } } @@ -1224,20 +1225,22 @@ describe('compiler compliance', () => { const $c2$ = ["if", ""]; const $c3$ = ["baz", ""]; const $c4$ = ["bar", ""]; - function MyComponent_div_span_Template_2(rf, ctx1, ctx0, ctx) { + function MyComponent_div_span_Template_2(rf, 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$.ɵx(); + const $bar$ = $r3$.ɵr(4); + $r3$.ɵx(); + const $foo$ = $r3$.ɵr(1); + const $baz$ = $r3$.ɵr(5); $r3$.ɵt(1, $r3$.ɵi3("", $foo$, "-", $bar$, "-", $baz$, "")); } } - function MyComponent_div_Template_3(rf, ctx0, ctx) { + function MyComponent_div_Template_3(rf, ctx) { if (rf & 1) { $r3$.ɵE(0, "div"); $r3$.ɵT(1); @@ -1246,8 +1249,9 @@ describe('compiler compliance', () => { $r3$.ɵe(); } if (rf & 2) { - const $foo$ = $r3$.ɵr(1, 1); - const $bar$ = $r3$.ɵld(4); + const $bar$ = $r3$.ɵr(4); + $r3$.ɵx(); + const $foo$ = $r3$.ɵr(1); $r3$.ɵt(1, $r3$.ɵi2(" ", $foo$, "-", $bar$, " ")); } } @@ -1264,7 +1268,7 @@ describe('compiler compliance', () => { $r3$.ɵEe(4, "div", null, $c3$); } if (rf & 2) { - const $foo$ = $r3$.ɵld(1); + const $foo$ = $r3$.ɵr(1); $r3$.ɵt(2, $r3$.ɵi1(" ", $foo$, " ")); } }, @@ -1278,6 +1282,77 @@ describe('compiler compliance', () => { }); + it('should support local refs mixed with context assignments', () => { + const files = { + app: { + 'spec.ts': ` + import {Component, NgModule} from '@angular/core'; + import {CommonModule} from '@angular/common'; + + @Component({ + selector: 'my-component', + template: \` +
+
+ + {{ foo }} - {{ item }} + +
\` + }) + export class MyComponent {} + + @NgModule({declarations: [MyComponent], imports: [CommonModule]}) + export class MyModule {} + ` + } + }; + + const template = ` + const $c0$ = ["ngFor","","ngForOf",""]; + const $c1$ = ["foo", ""]; + const $c2$ = ["ngIf",""]; + + function MyComponent_div_span_Template_3(rf, ctx) { + if (rf & 1) { + $i0$.ɵE(0, "span"); + $i0$.ɵT(1); + $i0$.ɵe(); + } + if (rf & 2) { + const $item$ = $i0$.ɵx().$implicit; + const $foo$ = $i0$.ɵr(2); + $i0$.ɵt(1, $i0$.ɵi2(" ", $foo$, " - ", $item$, " ")); + } + } + + function MyComponent_div_Template_0(rf, ctx) { + if (rf & 1) { + $i0$.ɵE(0, "div"); + $i0$.ɵEe(1, "div", null, $c1$); + $i0$.ɵC(3, MyComponent_div_span_Template_3, null, $c2$); + $i0$.ɵe(); + } + if (rf & 2) { + const $app$ = $i0$.ɵx(); + $i0$.ɵp(3, "ngIf", $i0$.ɵb($app$.showing)); + } + } + + // ... + template:function MyComponent_Template(rf, ctx){ + if (rf & 1) { + $i0$.ɵC(0, MyComponent_div_Template_0, null, $c0$); + } + if (rf & 2) { + $i0$.ɵp(0, "ngForOf", $i0$.ɵb(ctx.items)); + } + }`; + + const result = compile(files, angularFiles); + + expectEmit(result.source, template, 'Incorrect template'); + }); + describe('lifecycle hooks', () => { const files = { app: { @@ -1452,7 +1527,7 @@ describe('compiler compliance', () => { const MyComponentDefinition = ` const $_c0$ = ["for","","forOf",""]; - function MyComponent__svg_g_Template_1(rf, ctx0, ctx) { + function MyComponent__svg_g_Template_1(rf, ctx) { if (rf & 1) { $r3$.ɵNS(); $r3$.ɵE(0,"g"); @@ -1525,14 +1600,14 @@ describe('compiler compliance', () => { const MyComponentDefinition = ` const $_c0$ = ["for","","forOf",""]; - function MyComponent_li_Template_1(rf, ctx0, ctx) { + function MyComponent_li_Template_1(rf, ctx) { if (rf & 1) { $r3$.ɵE(0, "li"); $r3$.ɵT(1); $r3$.ɵe(); } if (rf & 2) { - const $item$ = ctx0.$implicit; + const $item$ = ctx.$implicit; $r3$.ɵt(1, $r3$.ɵi1("", $item$.name, "")); } } @@ -1602,20 +1677,20 @@ describe('compiler compliance', () => { const MyComponentDefinition = ` const $c1$ = ["for", "", "forOf", ""]; - function MyComponent_li_li_Template_4(rf, ctx1, ctx0, ctx) { + function MyComponent_li_li_Template_4(rf, ctx) { if (rf & 1) { $r3$.ɵE(0, "li"); $r3$.ɵT(1); $r3$.ɵe(); } if (rf & 2) { - const $item$ = ctx0.$implicit; - const $info$ = ctx1.$implicit; + const $info$ = ctx.$implicit; + const $item$ = $r3$.ɵx().$implicit; $r3$.ɵt(1, $r3$.ɵi2(" ", $item$.name, ": ", $info$.description, " ")); } } - function MyComponent_li_Template_1(rf, ctx0, ctx) { + function MyComponent_li_Template_1(rf, ctx) { if (rf & 1) { $r3$.ɵE(0, "li"); $r3$.ɵE(1, "div"); @@ -1627,7 +1702,7 @@ describe('compiler compliance', () => { $r3$.ɵe(); } if (rf & 2) { - const $item$ = ctx0.$implicit; + const $item$ = ctx.$implicit; $r3$.ɵt(2, $r3$.ɵi1("", IDENT.name, "")); $r3$.ɵp(4, "forOf", $r3$.ɵb(IDENT.infos)); } 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 3cfc3a2ecf..26a8448c33 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,56 +52,262 @@ 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$) { + function MyComponent_ul_li_div_Template_1(rf, ctx) { + if (rf & 1) { + const $inner$ = ctx.$implicit; + const $middle$ = $i0$.ɵx().$implicit; + const $outer$ = $i0$.ɵx().$implicit; + const $myComp$ = $i0$.ɵx(); $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$); + return $myComp$.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 $inner1$ = ctx.$implicit; + const $middle1$ = $i0$.ɵx().$implicit; + const $outer1$ = $i0$.ɵx().$implicit; + const $myComp1$ = $i0$.ɵx(); + $i0$.ɵp(0, "title", $i0$.ɵb($myComp1$.format($outer1$, $middle1$, $inner1$, $myComp1$.component))); + $i0$.ɵt(1, $i0$.ɵi1(" ", $myComp1$.format($outer1$, $middle1$, $inner1$, $myComp1$.component), " ")); } } - function MyComponent_ul_li_Template_1(rf, $ctx1$, $ctx0$, $ctx$) { + function MyComponent_ul_li_Template_1(rf, 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)); + const $myComp2$ = $i0$.ɵx(2); + $i0$.ɵp(1, "ngForOf", $i0$.ɵb($myComp2$.items)); } } - function MyComponent_ul_Template_0(rf, $ctx0$, $ctx$) { + function MyComponent_ul_Template_0(rf, 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)); + const $outer2$ = ctx.$implicit; + $i0$.ɵp(1, "ngForOf", $i0$.ɵb($outer2$.items)); } } // ... - template:function MyComponent_Template(rf, $ctx$){ + template:function MyComponent_Template(rf, ctx){ if (rf & 1) { $i0$.ɵC(0, MyComponent_ul_Template_0, null, _c0); } if (rf & 2) { - $i0$.ɵp(0, "ngForOf", $i0$.ɵb($ctx$.items)); + $i0$.ɵp(0, "ngForOf", $i0$.ɵb(ctx.items)); + } + }`; + + const result = compile(files, angularFiles); + + expectEmit(result.source, template, 'Incorrect template'); + }); + + it('should support ngFor context variables', () => { + const files = { + app: { + 'spec.ts': ` + import {Component, NgModule} from '@angular/core'; + import {CommonModule} from '@angular/common'; + + @Component({ + selector: 'my-component', + template: \` + + {{ i }} - {{ item }} + \` + }) + export class MyComponent {} + + @NgModule({declarations: [MyComponent], imports: [CommonModule]}) + export class MyModule {} + ` + } + }; + + const template = ` + const $c0$ = ["ngFor","","ngForOf",""]; + + function MyComponent_span_Template_0(rf, ctx) { + if (rf & 1) { + $i0$.ɵE(0, "span"); + $i0$.ɵT(1); + $i0$.ɵe(); + } + if (rf & 2) { + const $item$ = ctx.$implicit; + const $i$ = ctx.index; + $i0$.ɵt(1, $i0$.ɵi2(" ", $i$, " - ", $item$, " ")); + } + } + // ... + template:function MyComponent_Template(rf, ctx){ + if (rf & 1) { + $i0$.ɵC(0, MyComponent_span_Template_0, null, _c0); + } + if (rf & 2) { + $i0$.ɵp(0, "ngForOf", $i0$.ɵb(ctx.items)); + } + }`; + + const result = compile(files, angularFiles); + + expectEmit(result.source, template, 'Incorrect template'); + }); + + it('should support ngFor context variables in parent views', () => { + const files = { + app: { + 'spec.ts': ` + import {Component, NgModule} from '@angular/core'; + import {CommonModule} from '@angular/common'; + + @Component({ + selector: 'my-component', + template: \` +
+ + {{ i }} - {{ item }} + +
\` + }) + export class MyComponent {} + + @NgModule({declarations: [MyComponent], imports: [CommonModule]}) + export class MyModule {} + ` + } + }; + + const template = ` + const $c0$ = ["ngFor","","ngForOf",""]; + const $c1$ = ["ngIf",""]; + + function MyComponent_div_span_Template_1(rf, ctx) { + if (rf & 1) { + $i0$.ɵE(0, "span"); + $i0$.ɵT(1); + $i0$.ɵe(); + } + if (rf & 2) { + const $div$ = $i0$.ɵx(); + const $i$ = $div$.index; + const $item$ = $div$.$implicit; + $i0$.ɵt(1, $i0$.ɵi2(" ", $i$, " - ", $item$, " ")); + } + } + + function MyComponent_div_Template_0(rf, ctx) { + if (rf & 1) { + $i0$.ɵE(0, "div"); + $i0$.ɵC(1, MyComponent_div_span_Template_1, null, $c1$); + $i0$.ɵe(); + } + if (rf & 2) { + const $app$ = $i0$.ɵx(); + $i0$.ɵp(1, "ngIf", $i0$.ɵb($app$.showing)); + } + } + + // ... + template:function MyComponent_Template(rf, ctx){ + if (rf & 1) { + $i0$.ɵC(0, MyComponent_div_Template_0, null, $c0$); + } + if (rf & 2) { + $i0$.ɵp(0, "ngForOf", $i0$.ɵb(ctx.items)); + } + }`; + + const result = compile(files, angularFiles); + + expectEmit(result.source, template, 'Incorrect template'); + }); + + it('should correctly skip contexts as needed', () => { + const files = { + app: { + 'spec.ts': ` + import {Component, NgModule} from '@angular/core'; + import {CommonModule} from '@angular/common'; + + @Component({ + selector: 'my-component', + template: \` +
+
+
+ {{ middle.value }} - {{ name }} +
+
+
\` + }) + export class MyComponent {} + + @NgModule({declarations: [MyComponent], imports: [CommonModule]}) + export class MyModule {} + ` + } + }; + + // The template should look like this (where IDENT is a wild card for an identifier): + const template = ` + const $c0$ = ["ngFor","","ngForOf",""]; + function MyComponent_div_div_div_Template_1(rf, ctx) { + if (rf & 1) { + $i0$.ɵE(0, "div"); + $i0$.ɵT(1); + $i0$.ɵe(); + } + if (rf & 2) { + const $middle$ = $i0$.ɵx().$implicit; + const $myComp$ = $i0$.ɵx(2); + $i0$.ɵt(1, $i0$.ɵi2(" ", $middle$.value, " - ", $myComp$.name, " ")); + } + } + + function MyComponent_div_div_Template_1(rf, ctx) { + if (rf & 1) { + $i0$.ɵE(0, "div"); + $i0$.ɵC(1, MyComponent_div_div_div_Template_1, null, _c0); + $i0$.ɵe(); + } + if (rf & 2) { + const $middle$ = ctx.$implicit; + $i0$.ɵp(1, "ngForOf", $i0$.ɵb($middle$.items)); + } + } + + function MyComponent_div_Template_0(rf, ctx) { + if (rf & 1) { + $i0$.ɵE(0, "div"); + $i0$.ɵC(1, MyComponent_div_div_Template_1, null, _c0); + $i0$.ɵe(); + } + if (rf & 2) { + const $outer$ = ctx.$implicit; + $i0$.ɵp(1, "ngForOf", $i0$.ɵb($outer$.items)); + } + } + // ... + template:function MyComponent_Template(rf, ctx){ + if (rf & 1) { + $i0$.ɵC(0, MyComponent_div_Template_0, null, _c0); + } + if (rf & 2) { + $i0$.ɵp(0, "ngForOf", $i0$.ɵb(ctx.items)); } }`; diff --git a/packages/compiler/src/output/output_ast.ts b/packages/compiler/src/output/output_ast.ts index 46a592c770..7ab2775400 100644 --- a/packages/compiler/src/output/output_ast.ts +++ b/packages/compiler/src/output/output_ast.ts @@ -338,6 +338,8 @@ export class WriteVarExpr extends Expression { toDeclStmt(type?: Type|null, modifiers?: StmtModifier[]|null): DeclareVarStmt { return new DeclareVarStmt(this.name, this.value, type, modifiers, this.sourceSpan); } + + toConstDecl(): DeclareVarStmt { return this.toDeclStmt(INFERRED_TYPE, [StmtModifier.Final]); } } diff --git a/packages/compiler/src/render3/r3_identifiers.ts b/packages/compiler/src/render3/r3_identifiers.ts index 7acb4c0324..ff66593f12 100644 --- a/packages/compiler/src/render3/r3_identifiers.ts +++ b/packages/compiler/src/render3/r3_identifiers.ts @@ -45,6 +45,8 @@ export class Identifiers { static containerCreate: o.ExternalReference = {name: 'ɵC', moduleName: CORE}; + static nextContext: o.ExternalReference = {name: 'ɵx', moduleName: CORE}; + static text: o.ExternalReference = {name: 'ɵT', moduleName: CORE}; static textBinding: o.ExternalReference = {name: 'ɵt', moduleName: CORE}; diff --git a/packages/compiler/src/render3/view/compiler.ts b/packages/compiler/src/render3/view/compiler.ts index 5774da877c..0d58436fd4 100644 --- a/packages/compiler/src/render3/view/compiler.ts +++ b/packages/compiler/src/render3/view/compiler.ts @@ -149,7 +149,7 @@ export function compileComponentFromMetadata( const template = meta.template; const templateFunctionExpression = new TemplateDefinitionBuilder( - constantPool, CONTEXT_NAME, BindingScope.ROOT_SCOPE, 0, templateTypeName, templateName, + constantPool, BindingScope.ROOT_SCOPE, 0, templateTypeName, templateName, meta.viewQueries, directiveMatcher, directivesUsed, meta.pipes, pipesUsed, R3.namespaceHTML) .buildTemplateFunction( diff --git a/packages/compiler/src/render3/view/template.ts b/packages/compiler/src/render3/view/template.ts index dff44b5d3e..a67c4a40fb 100644 --- a/packages/compiler/src/render3/view/template.ts +++ b/packages/compiler/src/render3/view/template.ts @@ -57,12 +57,33 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver private _bindingContext = 0; private _prefixCode: o.Statement[] = []; private _creationCode: o.Statement[] = []; - private _variableCode: o.Statement[] = []; - private _bindingCode: (() => o.Statement)[] = []; - private _nestedTemplates: (() => void)[] = []; + /** + * List of callbacks to generate update mode instructions. We store them here as we process + * the template so bindings are resolved only once all nodes have been visited. This ensures + * all local refs and context variables are available for matching. + */ + private _updateCodeFns: (() => o.Statement)[] = []; + /** Temporary variable declarations generated from visiting pipes, literals, etc. */ + private _tempVariables: o.Statement[] = []; + /** + * List of callbacks to build nested templates. Nested templates must not be visited until + * after the parent template has finished visiting all of its nodes. This ensures that all + * local ref bindings in nested templates are able to find local ref values if the refs + * are defined after the template declaration. + */ + private _nestedTemplateFns: (() => void)[] = []; + /** + * This scope contains local variables declared in the update mode block of the template. + * (e.g. refs and context vars in bindings) + */ + private _updateScope: BindingScope; + /** + * This scope contains local variables declared in the creation mode block of the template + * (e.g. refs and context vars in listeners) + */ + private _creationScope: BindingScope; private _valueConverter: ValueConverter; private _unsupported = unsupported; - private _bindingScope: BindingScope; // Whether we are inside a translatable element (`

... somewhere here ...

) private _inI18nSection: boolean = false; @@ -74,20 +95,19 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver private _pureFunctionSlots = 0; constructor( - private constantPool: ConstantPool, private contextParameter: string, - parentBindingScope: BindingScope, private level = 0, private contextName: string|null, - private templateName: string|null, private viewQueries: R3QueryMetadata[], - private directiveMatcher: SelectorMatcher|null, private directives: Set, - private pipeTypeByName: Map, private pipes: Set, - private _namespace: o.ExternalReference) { + private constantPool: ConstantPool, parentBindingScope: BindingScope, private level = 0, + private contextName: string|null, private templateName: string|null, + private viewQueries: R3QueryMetadata[], private directiveMatcher: SelectorMatcher|null, + private directives: Set, private pipeTypeByName: Map, + private pipes: Set, private _namespace: o.ExternalReference) { // view queries can take up space in data and allocation happens earlier (in the "viewQuery" // function) this._dataIndex = viewQueries.length; - this._bindingScope = - parentBindingScope.nestedScope(level, (lhsVar: o.ReadVarExpr, rhsExpr: o.Expression) => { - this._variableCode.push( - lhsVar.set(rhsExpr).toDeclStmt(o.INFERRED_TYPE, [o.StmtModifier.Final])); - }); + + // TODO(kara): generate restore instruction in listener to replace creation scope + this._creationScope = parentBindingScope.nestedScope(level); + this._updateScope = parentBindingScope.nestedScope(level); + this._valueConverter = new ValueConverter( constantPool, () => this.allocateDataSlot(), (numSlots: number): number => this._pureFunctionSlots += numSlots, @@ -96,12 +116,33 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver if (pipeType) { this.pipes.add(pipeType); } - this._bindingScope.set(localName, value); + this._updateScope.set(this.level, localName, value); this._creationCode.push( o.importExpr(R3.pipe).callFn([o.literal(slot), o.literal(name)]).toStmt()); }); } + registerContextVariables(variable: t.Variable, retrievalScope: BindingScope) { + const scopedName = retrievalScope.freshReferenceName(); + const retrievalLevel = this.level; + const lhs = o.variable(variable.name + scopedName); + retrievalScope.set( + retrievalLevel, variable.name, lhs, DeclarationPriority.CONTEXT, + (scope: BindingScope, relativeLevel: number) => { + let rhs: o.Expression; + if (scope.bindingLevel === retrievalLevel) { + // e.g. ctx + rhs = o.variable(CONTEXT_NAME); + } else { + const sharedCtxVar = scope.getSharedContextName(retrievalLevel); + // e.g. ctx_r0 OR x(2); + rhs = sharedCtxVar ? sharedCtxVar : generateNextContextExpr(relativeLevel); + } + // e.g. const $item$ = x(2).$implicit; + return [lhs.set(rhs.prop(variable.value || IMPLICIT_REFERENCE)).toConstDecl()]; + }); + } + buildTemplateFunction( nodes: t.Node[], variables: t.Variable[], hasNgContent: boolean = false, ngContentSelectors: string[] = []): o.FunctionExpr { @@ -111,12 +152,9 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver // Create variable bindings for (const variable of variables) { - const variableName = variable.name; - const expression = - o.variable(this.contextParameter).prop(variable.value || IMPLICIT_REFERENCE); - const scopedName = this._bindingScope.freshReferenceName(); // Add the reference to the local scope. - this._bindingScope.set(variableName, o.variable(variableName + scopedName), expression); + this.registerContextVariables(variable, this._creationScope); + this.registerContextVariables(variable, this._updateScope); } // Output a `ProjectionDef` instruction when some `` are present @@ -135,42 +173,50 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver this.creationInstruction(null, R3.projectionDef, ...parameters); } + // This is the initial pass through the nodes of this template. In this pass, we + // generate all creation mode instructions & queue all update mode instructions for + // generation in the second pass. It's necessary to separate the passes to ensure + // local refs are defined before resolving bindings. t.visitAll(this, nodes); - const creationCode = this._creationCode.length > 0 ? - [renderFlagCheckIfStmt(core.RenderFlags.Create, this._creationCode)] : - []; - - const updateCode = this._bindingCode.length > 0 ? - [renderFlagCheckIfStmt(core.RenderFlags.Update, this._variableCode.concat( - this._variableCode.concat(this._bindingCode.map((fn: () => o.Statement) => fn()))))] : - []; + // Generate all the update mode instructions as the second pass (e.g. resolve bindings) + const updateStatements = this._updateCodeFns.map((fn: () => o.Statement) => fn()); + // To count slots for the reserveSlots() instruction, all bindings must have been visited. if (this._pureFunctionSlots > 0) { this.creationInstruction(null, R3.reserveSlots, o.literal(this._pureFunctionSlots)); } + const creationCode = this._creationCode.length > 0 ? + [renderFlagCheckIfStmt( + core.RenderFlags.Create, + this._creationScope.variableDeclarations().concat(this._creationCode))] : + []; + + // This must occur after binding resolution so we can generate context instructions that + // build on each other. e.g. const row = x().$implicit; const table = x().$implicit(); + const updateVariables = this._updateScope.variableDeclarations().concat(this._tempVariables); + + const updateCode = this._updateCodeFns.length > 0 ? + [renderFlagCheckIfStmt(core.RenderFlags.Update, updateVariables.concat(updateStatements))] : + []; + // Generate maps of placeholder name to node indexes // TODO(vicb): This is a WIP, not fully supported yet for (const phToNodeIdx of this._phToNodeIdxes) { if (Object.keys(phToNodeIdx).length > 0) { - const scopedName = this._bindingScope.freshReferenceName(); - const phMap = o.variable(scopedName) - .set(mapToExpression(phToNodeIdx, true)) - .toDeclStmt(o.INFERRED_TYPE, [o.StmtModifier.Final]); + const scopedName = this._updateScope.freshReferenceName(); + const phMap = o.variable(scopedName).set(mapToExpression(phToNodeIdx, true)).toConstDecl(); this._prefixCode.push(phMap); } } - this._nestedTemplates.forEach(buildTemplateFn => buildTemplateFn()); + this._nestedTemplateFns.forEach(buildTemplateFn => buildTemplateFn()); return o.fn( - // i.e. (rf: RenderFlags, ctx0: any, ctx: any) - [ - new o.FnParam(RENDER_FLAGS, o.NUMBER_TYPE), ...this.getNestedContexts(), - new o.FnParam(CONTEXT_NAME, null) - ], + // i.e. (rf: RenderFlags, ctx: any) + [new o.FnParam(RENDER_FLAGS, o.NUMBER_TYPE), new o.FnParam(CONTEXT_NAME, null)], [ // Temporary variable declarations for query refresh (i.e. let _t: any;) ...this._prefixCode, @@ -183,7 +229,7 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver } // LocalResolver - getLocal(name: string): o.Expression|null { return this._bindingScope.get(name); } + getLocal(name: string): o.Expression|null { return this._updateScope.get(name); } visitContent(ngContent: t.Content) { const slot = this.allocateDataSlot(); @@ -225,18 +271,6 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver 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 wasInI18nSection = this._inI18nSection; @@ -426,17 +460,19 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver const references = flatten(element.references.map(reference => { const slot = this.allocateDataSlot(); // Generate the update temporary. - const variableName = this._bindingScope.freshReferenceName(); - // 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) - ]); + const variableName = this._updateScope.freshReferenceName(); + const retrievalLevel = this.level; + const lhs = o.variable(variableName); + this._updateScope.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]; })); @@ -515,17 +551,11 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver const elName = sanitizeIdentifier(element.name); const evName = sanitizeIdentifier(outputAst.name); const functionName = `${this.templateName}_${elName}_${evName}_listener`; - const localVars: o.Statement[] = []; - 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])); - }); const bindingExpr = convertActionBinding( - bindingScope, implicit, outputAst.handler, 'b', + this._creationScope, implicit, outputAst.handler, 'b', () => error('Unexpected interpolation')); const handler = o.fn( - [new o.FnParam('$event', o.DYNAMIC_TYPE)], [...localVars, ...bindingExpr.render3Stmts], + [new o.FnParam('$event', o.DYNAMIC_TYPE)], [...bindingExpr.render3Stmts], o.INFERRED_TYPE, null, functionName); this.creationInstruction( outputAst.sourceSpan, R3.listener, o.literal(outputAst.name), handler); @@ -668,8 +698,6 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver const templateName = contextName ? `${contextName}_Template_${templateIndex}` : `Template_${templateIndex}`; - const templateContext = `ctx${this.level}`; - const parameters: o.Expression[] = [ o.literal(templateIndex), o.variable(templateName), @@ -713,15 +741,14 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver // Create the template function const templateVisitor = new TemplateDefinitionBuilder( - this.constantPool, templateContext, this._bindingScope, this.level + 1, contextName, - templateName, [], this.directiveMatcher, this.directives, this.pipeTypeByName, this.pipes, - this._namespace); + this.constantPool, this._updateScope, this.level + 1, contextName, templateName, [], + this.directiveMatcher, this.directives, this.pipeTypeByName, this.pipes, this._namespace); // 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(() => { + this._nestedTemplateFns.push(() => { const templateFunctionExpr = templateVisitor.buildTemplateFunction(template.children, template.variables); this.constantPool.statements.push(templateFunctionExpr.toDeclStmt(templateName, null)); @@ -790,7 +817,7 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver // bindings. e.g. {{ foo }}
private updateInstruction( span: ParseSourceSpan|null, reference: o.ExternalReference, paramsFn: () => o.Expression[]) { - this._bindingCode.push(() => { return this.instruction(span, reference, paramsFn()); }); + this._updateCodeFns.push(() => { return this.instruction(span, reference, paramsFn()); }); } private convertPropertyBinding(implicit: o.Expression, value: AST, skipBindFn?: boolean): @@ -800,7 +827,7 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver const convertedPropertyBinding = convertPropertyBinding( this, implicit, value, this.bindingContext(), BindingForm.TrySimple, interpolationFn); - this._variableCode.push(...convertedPropertyBinding.stmts); + this._tempVariables.push(...convertedPropertyBinding.stmts); const valExpr = convertedPropertyBinding.currValExpr; return value instanceof Interpolation || skipBindFn ? valExpr : @@ -888,6 +915,12 @@ function pureFunctionCallInfo(args: o.Expression[]) { }; } +// e.g. x(2); +function generateNextContextExpr(relativeLevelDiff: number): o.Expression { + return o.importExpr(R3.nextContext) + .callFn(relativeLevelDiff > 1 ? [o.literal(relativeLevelDiff)] : []); +} + function getLiteralFactory( constantPool: ConstantPool, literal: o.LiteralArrayExpr | o.LiteralMapExpr, allocateSlots: (numSlots: number) => number): o.Expression { @@ -919,33 +952,44 @@ function getLiteralFactory( * * It is expected that the function creates the `const localName = expression`; statement. */ -export type DeclareLocalVarCallback = (lhsVar: o.ReadVarExpr, rhsExpression: o.Expression) => void; +export type DeclareLocalVarCallback = (scope: BindingScope, relativeLevel: number) => o.Statement[]; + +/** The prefix used to get a shared context in BindingScope's map. */ +const SHARED_CONTEXT_KEY = '$$shared_ctx$$'; + +/** + * This is used when one refers to variable such as: 'let abc = x(2).$implicit`. + * - key to the map is the string literal `"abc"`. + * - value `retrievalLevel` is the level from which this value can be retrieved, which is 2 levels + * up in example. + * - value `lhs` is the left hand side which is an AST representing `abc`. + * - value `declareLocalCallback` is a callback that is invoked when declaring the local. + * - value `declare` is true if this value needs to be declared. + * - value `priority` dictates the sorting priority of this var declaration compared + * to other var declarations on the same retrieval level. For example, if there is a + * context variable and a local ref accessing the same parent view, the context var + * declaration should always come before the local ref declaration. + */ +type BindingData = { + retrievalLevel: number; lhs: o.ReadVarExpr; declareLocalCallback?: DeclareLocalVarCallback; + declare: boolean; + priority: number; +}; + +/** + * The sorting priority of a local variable declaration. Higher numbers + * mean the declaration will appear first in the generated code. + */ +const enum DeclarationPriority { DEFAULT = 0, CONTEXT = 1, SHARED_CONTEXT = 2 } export class BindingScope implements LocalResolver { - /** - * Keeps a map from local variables to their expressions. - * - * This is used when one refers to variable such as: 'let abc = a.b.c`. - * - key to the map is the string literal `"abc"`. - * - value `lhs` is the left hand side which is an AST representing `abc`. - * - value `rhs` is the right hand side which is an AST representing `a.b.c`. - * - value `declared` is true if the `declareLocalVarCallback` has been called for this scope - * already. - */ - private map = new Map < string, { - lhs: o.ReadVarExpr; - rhs: o.Expression|undefined; - declared: boolean; - rhsCallback?: (level: number) => o.Expression; - } - > (); + /** Keeps a map from local variables to their BindingData. */ + private map = new Map(); private referenceNameIndex = 0; - static ROOT_SCOPE = new BindingScope().set('$event', o.variable('$event')); + static ROOT_SCOPE = new BindingScope().set(-1, '$event', o.variable('$event')); - private constructor( - private level: number = 0, private parent: BindingScope|null = null, - private declareLocalVarCallback: DeclareLocalVarCallback = noop) {} + private constructor(public bindingLevel: number = 0, private parent: BindingScope|null = null) {} get(name: string): o.Expression|null { let current: BindingScope|null = this; @@ -953,48 +997,118 @@ export class BindingScope implements LocalResolver { let value = current.map.get(name); if (value != null) { if (current !== this) { - // make a local copy and reset the `declared` state. - value = {lhs: value.lhs, rhs: value.rhs, rhsCallback: value.rhsCallback, declared: false}; + // make a local copy and reset the `declare` state + value = { + retrievalLevel: value.retrievalLevel, + lhs: value.lhs, + declareLocalCallback: value.declareLocalCallback, + declare: false, + priority: value.priority + }; + // Cache the value locally. this.map.set(name, value); + // Possibly generate a shared context var + this.maybeGenerateSharedContextVar(value); } - 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 - // then invoke the callback to insert variable declaration. - this.declareLocalVarCallback(value.lhs, rhs); - value.declared = true; + + if (value.declareLocalCallback && !value.declare) { + value.declare = true; } return value.lhs; } current = current.parent; } - return null; + + // If we get to this point, we are looking for a property on the top level component + // - If level === 0, we are on the top and don't need to re-declare `ctx`. + // - If level > 0, we are in an embedded view. We need to retrieve the name of the + // local var we used to store the component context, e.g. const $comp$ = x(); + return this.bindingLevel === 0 ? null : this.getComponentProperty(name); } /** * Create a local variable for later reference. * + * @param retrievalLevel The level from which this value can be retrieved * @param name Name of the variable. * @param lhs AST representing the left hand side of the `let lhs = rhs;`. - * @param rhs AST representing the right hand side of the `let lhs = rhs;`. The `rhs` can be - * `undefined` for variable that are ambient such as `$event` and which don't have `rhs` - * declaration. + * @param priority The sorting priority of this var + * @param declareLocalCallback The callback to invoke when declaring this local var */ - set(name: string, lhs: o.ReadVarExpr, rhs?: o.Expression, - rhsCallback?: (level: number) => o.Expression): BindingScope { + set(retrievalLevel: number, name: string, lhs: o.ReadVarExpr, + priority: number = DeclarationPriority.DEFAULT, + declareLocalCallback?: DeclareLocalVarCallback): 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, rhsCallback: rhsCallback}); + this.map.set(name, { + retrievalLevel: retrievalLevel, + lhs: lhs, + declare: false, + declareLocalCallback: declareLocalCallback, + priority: priority + }); return this; } getLocal(name: string): (o.Expression|null) { return this.get(name); } - nestedScope(level: number, declareCallback: DeclareLocalVarCallback): BindingScope { - return new BindingScope(level, this, declareCallback); + nestedScope(level: number): BindingScope { + const newScope = new BindingScope(level, this); + if (level > 0) newScope.generateSharedContextVar(0); + return newScope; } + getSharedContextName(retrievalLevel: number): o.ReadVarExpr|null { + const sharedCtxObj = this.map.get(SHARED_CONTEXT_KEY + retrievalLevel); + return sharedCtxObj && sharedCtxObj.declare ? sharedCtxObj.lhs : null; + } + + maybeGenerateSharedContextVar(value: BindingData) { + if (value.priority === DeclarationPriority.CONTEXT) { + const sharedCtxObj = this.map.get(SHARED_CONTEXT_KEY + value.retrievalLevel); + if (sharedCtxObj) { + sharedCtxObj.declare = true; + } else { + this.generateSharedContextVar(value.retrievalLevel); + } + } + } + + generateSharedContextVar(retrievalLevel: number) { + const lhs = o.variable(CONTEXT_NAME + this.freshReferenceName()); + this.map.set(SHARED_CONTEXT_KEY + retrievalLevel, { + retrievalLevel: retrievalLevel, + lhs: lhs, + declareLocalCallback: (scope: BindingScope, relativeLevel: number) => { + // const ctx_r0 = x(2); + return [lhs.set(generateNextContextExpr(relativeLevel)).toConstDecl()]; + }, + declare: false, + priority: DeclarationPriority.SHARED_CONTEXT + }); + } + + getComponentProperty(name: string): o.Expression { + const componentValue = this.map.get(SHARED_CONTEXT_KEY + 0) !; + componentValue.declare = true; + return componentValue.lhs.prop(name); + } + + variableDeclarations(): o.Statement[] { + let currentContextLevel = 0; + return Array.from(this.map.values()) + .filter(value => value.declare) + .sort((a, b) => b.retrievalLevel - a.retrievalLevel || b.priority - a.priority) + .reduce((stmts: o.Statement[], value: BindingData) => { + const levelDiff = this.bindingLevel - value.retrievalLevel; + const currStmts = value.declareLocalCallback !(this, levelDiff - currentContextLevel); + currentContextLevel = levelDiff; + return stmts.concat(currStmts); + }, []) as o.Statement[]; + } + + freshReferenceName(): string { let current: BindingScope = this; // Find the top scope as it maintains the global reference count diff --git a/packages/core/src/core_render3_private_export.ts b/packages/core/src/core_render3_private_export.ts index 4b6fe18f89..3d64d0b715 100644 --- a/packages/core/src/core_render3_private_export.ts +++ b/packages/core/src/core_render3_private_export.ts @@ -32,6 +32,7 @@ export { NgModuleFactory as ɵNgModuleFactory, NC as ɵNC, C as ɵC, + x as ɵx, E as ɵE, NH as ɵNH, NM as ɵNM, diff --git a/packages/core/src/render3/component_ref.ts b/packages/core/src/render3/component_ref.ts index 747adbcc7b..3f7ba268da 100644 --- a/packages/core/src/render3/component_ref.ts +++ b/packages/core/src/render3/component_ref.ts @@ -22,7 +22,7 @@ import {baseDirectiveCreate, createLNode, createLViewData, createTView, elementC import {ComponentDefInternal, ComponentType, RenderFlags} from './interfaces/definition'; import {LElementNode, TNode, TNodeType} from './interfaces/node'; import {RElement, domRendererFactory3} from './interfaces/renderer'; -import {FLAGS, INJECTOR, LViewData, LViewFlags, RootContext, TVIEW} from './interfaces/view'; +import {CONTEXT, FLAGS, INJECTOR, LViewData, LViewFlags, RootContext, TVIEW} from './interfaces/view'; import {ViewRef} from './view_ref'; export class ComponentFactoryResolver extends viewEngine_ComponentFactoryResolver { @@ -120,6 +120,7 @@ export class ComponentFactory extends viewEngine_ComponentFactory { rootContext.components.push( component = baseDirectiveCreate(0, this.componentDef.factory(), this.componentDef) as T); initChangeDetectorIfExisting(elementNode.nodeInjector, component, elementNode.data !); + (elementNode.data as LViewData)[CONTEXT] = component; // TODO: should LifecycleHooksFeature and other host features be generated by the compiler and // executed here? diff --git a/packages/core/src/render3/di.ts b/packages/core/src/render3/di.ts index f8f89c49e4..0ff99def26 100644 --- a/packages/core/src/render3/di.ts +++ b/packages/core/src/render3/di.ts @@ -738,8 +738,8 @@ class TemplateRef implements viewEngine.TemplateRef { readonly elementRef: viewEngine.ElementRef; constructor( - private _declarationParentView: LViewData, elementRef: viewEngine.ElementRef, private _tView: TView, private _renderer: Renderer3, - private _queries: LQueries|null) { + private _declarationParentView: LViewData, elementRef: viewEngine.ElementRef, + private _tView: TView, private _renderer: Renderer3, private _queries: LQueries|null) { this.elementRef = elementRef; } diff --git a/packages/core/src/render3/index.ts b/packages/core/src/render3/index.ts index 69915756d9..c9da55ea79 100644 --- a/packages/core/src/render3/index.ts +++ b/packages/core/src/render3/index.ts @@ -49,6 +49,8 @@ export { containerRefreshStart as cR, containerRefreshEnd as cr, + nextContext as x, + element as Ee, elementAttribute as a, elementClassProp as cp, diff --git a/packages/core/src/render3/instructions.ts b/packages/core/src/render3/instructions.ts index 4d48cdf06d..91140381aa 100644 --- a/packages/core/src/render3/instructions.ts +++ b/packages/core/src/render3/instructions.ts @@ -16,7 +16,7 @@ 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, EmbeddedTemplate, InitialStylingFlags, PipeDefListOrFactory, RenderFlags} from './interfaces/definition'; +import {ComponentDefInternal, ComponentQuery, ComponentTemplate, DirectiveDefInternal, DirectiveDefListOrFactory, 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'; @@ -165,6 +165,14 @@ export function getCreationMode(): boolean { */ let viewData: LViewData; +/** + * The last viewData retrieved by nextContext(). + * Allows building nextContext() and reference() calls. + * + * e.g. const inner = x().$implicit; const outer = x().$implicit; + */ +let contextViewData: LViewData = null !; + /** * An array of directive instances in the current view. * @@ -223,7 +231,7 @@ export function enterView(newView: LViewData, host: LElementNode | LViewNode | n isParent = true; } - viewData = newView; + viewData = contextViewData = newView; currentQueries = newView && newView[QUERIES]; return oldView; @@ -547,7 +555,7 @@ export function renderEmbeddedTemplate( oldView = enterView(viewNode.data !, viewNode); namespaceHTML(); - callTemplateWithContexts(rf, context, tView.template !, viewNode.data ![DECLARATION_VIEW] !); + tView.template !(rf, context); if (rf & RenderFlags.Update) { refreshDescendantViews(); } else { @@ -566,113 +574,18 @@ export function renderEmbeddedTemplate( } /** - * This function calls the template function of a dynamically created view with - * all of its declaration parent contexts (up the view tree) until it reaches the - * component boundary. + * Retrieves a context at the level specified and saves it as the global, contextViewData. + * Will get the next level up if level is not specified. * - * Example: + * This is used to save contexts of parent views so they can be bound in embedded views, or + * in conjunction with reference() to bind a ref from a parent view. * - * AppComponent template: - *
    - *
  • {{ item }}
  • - *
- * - * 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 - * parent (the ul), and the ul's parent (AppComponent). - * - * Note that a declaration parent is NOT always the same as the insertion parent. Templates - * can be declared in different views than they are used. - * - * @param rf The RenderFlags for this template invocation - * @param currentContext The context for this template - * @param template The template function to call - * @param parentView The declaration view of the dynamic view + * @param level The relative level of the view from which to grab context compared to contextVewData + * @returns context */ -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 (parentView2 === null) { - return template(rf, currentContext, parentContext); - } - - const parentContext2 = parentView2[CONTEXT]; - const parentView3 = parentView2[DECLARATION_VIEW]; - if (parentView3 === null) { - return template(rf, currentContext, parentContext, parentContext2); - } - - const parentContext3 = parentView3[CONTEXT]; - const parentView4 = parentView3[DECLARATION_VIEW]; - if (parentView4 === null) { - return template(rf, currentContext, parentContext, parentContext2, parentContext3); - } - - const parentContext4 = parentView4[CONTEXT]; - const parentView5 = parentView4[DECLARATION_VIEW]; - if (parentView5 === null) { - return template( - rf, currentContext, parentContext, parentContext2, parentContext3, parentContext4); - } - - const parentContext5 = parentView5[CONTEXT]; - const parentView6 = parentView5[DECLARATION_VIEW]; - if (parentView6 === null) { - return template( - rf, currentContext, parentContext, parentContext2, parentContext3, parentContext4, - parentContext5); - } - - const parentContext6 = parentView6[CONTEXT]; - const parentView7 = parentView6[DECLARATION_VIEW]; - if (parentView7 === null) { - return template( - 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 templateArgs = [ - rf, currentContext, parentContext, parentContext2, parentContext3, parentContext4, - parentContext5, parentContext6, parentContext7, parentContext8, parentView9[CONTEXT] - ]; - - let currentDeclarationView: LViewData|null = parentView9[DECLARATION_VIEW]; - while (currentDeclarationView) { - templateArgs.push(currentDeclarationView[CONTEXT]); - currentDeclarationView = currentDeclarationView[DECLARATION_VIEW] !; - } - - template.apply(null, templateArgs); +export function nextContext(level: number = 1): any { + contextViewData = walkUpViews(level, contextViewData !); + return contextViewData[CONTEXT]; } export function renderComponentOrTemplate( @@ -1007,7 +920,7 @@ function getOrCreateTView( * @param pipes Registry of pipes for this view */ export function createTView( - viewIndex: number, template: ComponentTemplate| EmbeddedTemplate| null, + viewIndex: number, template: ComponentTemplate| null, directives: DirectiveDefListOrFactory | null, pipes: PipeDefListOrFactory | null, viewQuery: ComponentQuery| null): TView { ngDevMode && ngDevMode.tView++; @@ -1828,7 +1741,7 @@ export function createLContainer( * @param localRefs A set of local reference bindings on the element. */ export function container( - index: number, template?: EmbeddedTemplate, tagName?: string | null, attrs?: TAttributes, + index: number, template?: ComponentTemplate, tagName?: string | null, attrs?: TAttributes, localRefs?: string[] | null): void { ngDevMode && assertEqual( @@ -2653,9 +2566,19 @@ 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; +/** + * Retrieves a local reference from the current contextViewData. + * + * If the reference to retrieve is in a parent view, this instruction is used in conjunction + * with a nextContext() call, which walks up the tree and updates the contextViewData instance. + * + * @param index The index of the local ref in contextViewData. + */ +export function reference(index: number) { + return loadInternal(index, contextViewData); +} + +function walkUpViews(nestingLevel: number, currentView: LViewData): LViewData { while (nestingLevel > 0) { ngDevMode && assertDefined( currentView[DECLARATION_VIEW], @@ -2663,8 +2586,7 @@ export function reference(nestingLevel: number, index: number) { currentView = currentView[DECLARATION_VIEW] !; nestingLevel--; } - - return loadInternal(index, currentView); + return currentView; } /** Retrieves a value from the `directives` array. */ diff --git a/packages/core/src/render3/interfaces/definition.ts b/packages/core/src/render3/interfaces/definition.ts index 111840f9c8..7b321c38b5 100644 --- a/packages/core/src/render3/interfaces/definition.ts +++ b/packages/core/src/render3/interfaces/definition.ts @@ -19,13 +19,6 @@ export type ComponentTemplate = { (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; -}; - /** * Definition of what a query function should look like. */ diff --git a/packages/core/src/render3/jit/environment.ts b/packages/core/src/render3/jit/environment.ts index 24ae08319d..5c02dd224c 100644 --- a/packages/core/src/render3/jit/environment.ts +++ b/packages/core/src/render3/jit/environment.ts @@ -36,6 +36,7 @@ export const angularCoreEnv: {[name: string]: Function} = { 'ɵa': r3.a, 'ɵb': r3.b, 'ɵC': r3.C, + 'ɵx': r3.x, 'ɵcR': r3.cR, 'ɵcr': r3.cr, 'ɵd': r3.d, diff --git a/packages/core/src/render3/view_ref.ts b/packages/core/src/render3/view_ref.ts index b812568247..1b23cf216c 100644 --- a/packages/core/src/render3/view_ref.ts +++ b/packages/core/src/render3/view_ref.ts @@ -12,7 +12,6 @@ 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'; @@ -244,30 +243,3 @@ 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 fabcd26ce5..d4d1870f4c 100644 --- a/packages/core/test/bundling/hello_world/bundle.golden_symbols.json +++ b/packages/core/test/bundling/hello_world/bundle.golden_symbols.json @@ -17,9 +17,6 @@ { "name": "ChangeDetectionStrategy" }, - { - "name": "DECLARATION_VIEW" - }, { "name": "DIRECTIVES" }, @@ -101,9 +98,6 @@ { "name": "callHooks" }, - { - "name": "callTemplateWithContexts" - }, { "name": "canInsertNativeNode" }, diff --git a/packages/core/test/bundling/todo/bundle.golden_symbols.json b/packages/core/test/bundling/todo/bundle.golden_symbols.json index 3cf638fdc6..d3daec9e7a 100644 --- a/packages/core/test/bundling/todo/bundle.golden_symbols.json +++ b/packages/core/test/bundling/todo/bundle.golden_symbols.json @@ -326,9 +326,6 @@ { "name": "callHooks" }, - { - "name": "callTemplateWithContexts" - }, { "name": "canInsertNativeNode" }, @@ -347,6 +344,9 @@ { "name": "container" }, + { + "name": "contextViewData" + }, { "name": "createDirectivesAndLocals" }, @@ -692,6 +692,9 @@ { "name": "namespaceHTML" }, + { + "name": "nextContext" + }, { "name": "pointers" }, @@ -725,9 +728,6 @@ { "name": "readElementValue" }, - { - "name": "reference" - }, { "name": "refreshChildComponents" }, @@ -869,6 +869,9 @@ { "name": "walkLNodeTree" }, + { + "name": "walkUpViews" + }, { "name": "wrapListenerWithDirtyAndDefault" }, diff --git a/packages/core/test/render3/common_integration_spec.ts b/packages/core/test/render3/common_integration_spec.ts index 0e7a49d728..91fea58aa2 100644 --- a/packages/core/test/render3/common_integration_spec.ts +++ b/packages/core/test/render3/common_integration_spec.ts @@ -10,7 +10,7 @@ import {NgForOfContext} from '@angular/common'; import {getOrCreateNodeInjectorForNode, getOrCreateTemplateRef} from '../../src/render3/di'; import {AttributeMarker, defineComponent} from '../../src/render3/index'; -import {bind, container, elementEnd, elementProperty, elementStart, interpolation1, interpolation2, interpolation3, interpolationV, listener, load, text, textBinding} from '../../src/render3/instructions'; +import {bind, container, elementEnd, elementProperty, elementStart, interpolation1, interpolation2, interpolation3, interpolationV, listener, load, nextContext, text, textBinding} from '../../src/render3/instructions'; import {RenderFlags} from '../../src/render3/interfaces/definition'; import {NgForOf, NgIf, NgTemplateOutlet} from './common_with_def'; @@ -20,6 +20,18 @@ describe('@angular/common integration', () => { describe('NgForOf', () => { it('should update a loop', () => { + function liTemplate(rf: RenderFlags, ctx: NgForOfContext) { + if (rf & RenderFlags.Create) { + elementStart(0, 'li'); + { text(1); } + elementEnd(); + } + if (rf & RenderFlags.Update) { + const item = ctx.$implicit; + textBinding(1, bind(item)); + } + } + class MyApp { items: string[] = ['first', 'second']; @@ -30,25 +42,14 @@ describe('@angular/common integration', () => { //
    //
  • {{item}}
  • //
- template: (rf: RenderFlags, myApp: MyApp) => { + template: (rf: RenderFlags, ctx: MyApp) => { if (rf & RenderFlags.Create) { elementStart(0, 'ul'); { container(1, liTemplate, undefined, ['ngForOf', '']); } elementEnd(); } if (rf & RenderFlags.Update) { - elementProperty(1, 'ngForOf', bind(myApp.items)); - } - - function liTemplate(rf1: RenderFlags, row: NgForOfContext, parent: MyApp) { - if (rf1 & RenderFlags.Create) { - elementStart(0, 'li'); - { text(1); } - elementEnd(); - } - if (rf1 & RenderFlags.Update) { - textBinding(1, bind(row.$implicit)); - } + elementProperty(1, 'ngForOf', bind(ctx.items)); } }, directives: () => [NgForOf] @@ -79,6 +80,17 @@ describe('@angular/common integration', () => { }); it('should support ngForOf context variables', () => { + function liTemplate(rf: RenderFlags, ctx: NgForOfContext) { + if (rf & RenderFlags.Create) { + elementStart(0, 'li'); + { text(1); } + elementEnd(); + } + if (rf & RenderFlags.Update) { + const item = ctx.$implicit; + textBinding(1, interpolation3('', ctx.index, ' of ', ctx.count, ': ', item, '')); + } + } class MyApp { items: string[] = ['first', 'second']; @@ -88,29 +100,19 @@ describe('@angular/common integration', () => { factory: () => new MyApp(), selectors: [['my-app']], //
    - //
  • {{index}} of {{count}}: {{item}}
  • + //
  • {{index}} of + // {{count}}: {{item}}
  • //
- template: (rf: RenderFlags, myApp: MyApp) => { + template: (rf: RenderFlags, ctx: MyApp) => { if (rf & RenderFlags.Create) { elementStart(0, 'ul'); { container(1, liTemplate, undefined, ['ngForOf', '']); } elementEnd(); } if (rf & RenderFlags.Update) { - elementProperty(1, 'ngForOf', bind(myApp.items)); + elementProperty(1, 'ngForOf', bind(ctx.items)); } - function liTemplate(rf1: RenderFlags, row: NgForOfContext, parent: MyApp) { - if (rf1 & RenderFlags.Create) { - elementStart(0, 'li'); - { text(1); } - elementEnd(); - } - if (rf1 & RenderFlags.Update) { - textBinding( - 1, interpolation3('', row.index, ' of ', row.count, ': ', row.$implicit, '')); - } - } }, directives: () => [NgForOf] }); @@ -128,6 +130,18 @@ describe('@angular/common integration', () => { it('should retain parent view listeners when the NgFor destroy views', () => { + function liTemplate(rf: RenderFlags, ctx: NgForOfContext) { + if (rf & RenderFlags.Create) { + elementStart(0, 'li'); + { text(1); } + elementEnd(); + } + if (rf & RenderFlags.Update) { + const item = ctx.$implicit; + textBinding(1, interpolation1('', item, '')); + } + } + class MyApp { private _data: number[] = [1, 2, 3]; items: number[] = []; @@ -148,11 +162,11 @@ describe('@angular/common integration', () => { //
    //
  • {{index}}
  • //
- template: (rf: RenderFlags, myApp: MyApp) => { + template: (rf: RenderFlags, ctx: MyApp) => { if (rf & RenderFlags.Create) { elementStart(0, 'button'); { - listener('click', function() { return myApp.toggle(); }); + listener('click', function() { return ctx.toggle(); }); text(1, 'Toggle List'); } elementEnd(); @@ -161,19 +175,9 @@ describe('@angular/common integration', () => { elementEnd(); } if (rf & RenderFlags.Update) { - elementProperty(3, 'ngForOf', bind(myApp.items)); + elementProperty(3, 'ngForOf', bind(ctx.items)); } - function liTemplate(rf1: RenderFlags, row: NgForOfContext, parent: MyApp) { - if (rf1 & RenderFlags.Create) { - elementStart(0, 'li'); - { text(1); } - elementEnd(); - } - if (rf1 & RenderFlags.Update) { - textBinding(1, interpolation1('', row.$implicit, '')); - } - } }, directives: () => [NgForOf] }); @@ -205,7 +209,8 @@ describe('@angular/common integration', () => { /** *
    *
  • - * {{cell}} - {{ row.value }} + * {{cell}} - {{ row.value }} - {{ items.length }} + * *
  • *
*/ @@ -216,14 +221,14 @@ describe('@angular/common integration', () => { type: MyApp, factory: () => new MyApp(), selectors: [['my-app']], - template: (rf: RenderFlags, myApp: MyApp) => { + template: (rf: RenderFlags, ctx: MyApp) => { if (rf & RenderFlags.Create) { elementStart(0, 'ul'); { container(1, liTemplate, null, ['ngForOf', '']); } elementEnd(); } if (rf & RenderFlags.Update) { - elementProperty(1, 'ngForOf', bind(myApp.items)); + elementProperty(1, 'ngForOf', bind(ctx.items)); } }, @@ -231,27 +236,29 @@ describe('@angular/common integration', () => { }); } - function liTemplate(rf1: RenderFlags, row: any, myApp: MyApp) { - if (rf1 & RenderFlags.Create) { + function liTemplate(rf: RenderFlags, ctx: any) { + if (rf & 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)); + if (rf & RenderFlags.Update) { + const row = ctx.$implicit as any; + elementProperty(1, 'ngForOf', bind(row.data)); } } - function spanTemplate(rf1: RenderFlags, cell: any, row: any, myApp: MyApp) { - if (rf1 & RenderFlags.Create) { + function spanTemplate(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { elementStart(0, 'span'); { text(1); } elementEnd(); } - if (rf1 & RenderFlags.Update) { - textBinding( - 1, interpolation2('', cell.$implicit, ' - ', (row.$implicit as any).value, '')); + if (rf & RenderFlags.Update) { + const cell = ctx.$implicit; + const row = nextContext().$implicit as any; + const app = nextContext() as any; + textBinding(1, interpolation3('', cell, ' - ', row.value, ' - ', app.items.length, '')); } } @@ -261,26 +268,198 @@ describe('@angular/common integration', () => { fixture.update(); expect(fixture.html) .toEqual( - '
  • 1 - first2 - first
  • 3 - second4 - second
'); + '
  • 1 - first - 22 - first - 2
  • 3 - second - 24 - second - 2
'); // Remove the last item fixture.component.items.length = 1; fixture.update(); expect(fixture.html) - .toEqual('
  • 1 - first2 - first
'); + .toEqual('
  • 1 - first - 12 - first - 1
'); // Change an item fixture.component.items[0].data[0] = 'one'; fixture.update(); expect(fixture.html) - .toEqual('
  • one - first2 - first
'); + .toEqual('
  • one - first - 12 - first - 1
'); // Add an item fixture.component.items[1] = {data: ['three', '4'], value: 'third'}; fixture.update(); expect(fixture.html) .toEqual( - '
  • one - first2 - first
  • three - third4 - third
'); + '
  • one - first - 22 - first - 2
  • three - third - 24 - third - 2
'); + }); + + it('should support multiple levels of embedded templates with listeners', () => { + /** + *
+ *

+ * + * {{ row.value }} - {{ name }} + *

+ *
+ */ + class MyApp { + items: any[] = [{data: ['1'], value: 'first'}]; + name = 'app'; + events: string[] = []; + + onClick(value: string, name: string) { this.events.push(value, name); } + + static ngComponentDef = defineComponent({ + type: MyApp, + factory: () => new MyApp(), + selectors: [['my-app']], + template: (rf: RenderFlags, ctx: MyApp) => { + if (rf & RenderFlags.Create) { + container(0, divTemplate, null, ['ngForOf', '']); + } + if (rf & RenderFlags.Update) { + elementProperty(0, 'ngForOf', bind(ctx.items)); + } + + }, + directives: () => [NgForOf] + }); + } + + function divTemplate(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + elementStart(0, 'div'); + { container(1, pTemplate, null, ['ngForOf', '']); } + elementEnd(); + } + if (rf & RenderFlags.Update) { + const row = ctx.$implicit as any; + elementProperty(1, 'ngForOf', bind(row.data)); + } + } + + function pTemplate(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + const row = nextContext().$implicit as any; + const app = nextContext(); + elementStart(0, 'p'); + { + elementStart(1, 'span'); + { + listener('click', () => { app.onClick(row.value, app.name); }); + } + elementEnd(); + text(2); + } + elementEnd(); + } + if (rf & RenderFlags.Update) { + const row = nextContext().$implicit as any; + const app = nextContext() as any; + textBinding(2, interpolation2('', row.value, ' - ', app.name, '')); + } + } + + const fixture = new ComponentFixture(MyApp); + + fixture.update(); + expect(fixture.html).toEqual('

first - app

'); + + const span = fixture.hostElement.querySelector('span') as any; + span.click(); + expect(fixture.component.events).toEqual(['first', 'app']); + + fixture.component.name = 'new name'; + fixture.update(); + expect(fixture.html).toEqual('

first - new name

'); + + span.click(); + expect(fixture.component.events).toEqual(['first', 'app', 'first', 'new name']); + }); + + it('should support skipping contexts', () => { + /** + *
+ *
+ * + * {{ cell.value }} - {{ name }} + * + *
+ *
+ */ + class MyApp { + name = 'app'; + items: any[] = [ + [ + // row + {value: 'one', data: ['1', '2']} // cell + ], + [{value: 'two', data: ['3', '4']}] + ]; + + static ngComponentDef = defineComponent({ + type: MyApp, + factory: () => new MyApp(), + selectors: [['my-app']], + template: (rf: RenderFlags, ctx: MyApp) => { + if (rf & RenderFlags.Create) { + container(0, divTemplate, null, ['ngForOf', '']); + } + if (rf & RenderFlags.Update) { + elementProperty(0, 'ngForOf', bind(ctx.items)); + } + + }, + directives: () => [NgForOf] + }); + } + + function divTemplate(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + elementStart(0, 'div'); + { container(1, innerDivTemplate, null, ['ngForOf', '']); } + elementEnd(); + } + if (rf & RenderFlags.Update) { + const row = ctx.$implicit as any; + elementProperty(1, 'ngForOf', bind(row)); + } + } + + function innerDivTemplate(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + elementStart(0, 'div'); + { container(1, spanTemplate, null, ['ngForOf', '']); } + elementEnd(); + } + if (rf & RenderFlags.Update) { + const cell = ctx.$implicit as any; + elementProperty(1, 'ngForOf', bind(cell.data)); + } + } + + function spanTemplate(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + elementStart(0, 'span'); + { text(1); } + elementEnd(); + } + if (rf & RenderFlags.Update) { + const cell = nextContext().$implicit as any; + const app = nextContext(2) as any; + textBinding(1, interpolation2('', cell.value, ' - ', app.name, '')); + } + } + + const fixture = new ComponentFixture(MyApp); + + fixture.update(); + expect(fixture.html) + .toEqual( + `
one - appone - app
two - apptwo - app
`); + + fixture.component.name = 'other'; + fixture.update(); + expect(fixture.html) + .toEqual( + `
one - otherone - other
two - othertwo - other
`); }); it('should support context for 9+ levels of embedded templates', () => { @@ -385,12 +564,12 @@ describe('@angular/common integration', () => { type: MyApp, factory: () => new MyApp(), selectors: [['my-app']], - template: (rf: RenderFlags, myApp: MyApp) => { + template: (rf: RenderFlags, ctx: MyApp) => { if (rf & RenderFlags.Create) { container(0, itemTemplate0, null, ['ngForOf', '']); } if (rf & RenderFlags.Update) { - elementProperty(0, 'ngForOf', bind(myApp.items)); + elementProperty(0, 'ngForOf', bind(ctx.items)); } }, @@ -398,128 +577,125 @@ describe('@angular/common integration', () => { }); } - function itemTemplate0(rf1: RenderFlags, item0: any, myApp: MyApp) { - if (rf1 & RenderFlags.Create) { + function itemTemplate0(rf: RenderFlags, ctx: any) { + if (rf & 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)); + if (rf & RenderFlags.Update) { + const item0 = ctx.$implicit as any; + elementProperty(1, 'ngForOf', bind(item0.data)); } } - function itemTemplate1(rf1: RenderFlags, item1: any, item0: any, myApp: MyApp) { - if (rf1 & RenderFlags.Create) { + function itemTemplate1(rf: RenderFlags, ctx: any) { + if (rf & 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)); + if (rf & RenderFlags.Update) { + const item1 = ctx.$implicit as any; + elementProperty(1, 'ngForOf', bind(item1.data)); } } - function itemTemplate2(rf1: RenderFlags, item2: any, item1: any, item0: any, myApp: MyApp) { - if (rf1 & RenderFlags.Create) { + function itemTemplate2(rf: RenderFlags, ctx: any) { + if (rf & 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)); + if (rf & RenderFlags.Update) { + const item2 = ctx.$implicit as any; + elementProperty(1, 'ngForOf', bind(item2.data)); } } - function itemTemplate3( - rf1: RenderFlags, item3: any, item2: any, item1: any, item0: any, myApp: MyApp) { - if (rf1 & RenderFlags.Create) { + function itemTemplate3(rf: RenderFlags, ctx: any) { + if (rf & 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)); + if (rf & RenderFlags.Update) { + const item3 = ctx.$implicit as any; + elementProperty(1, 'ngForOf', bind(item3.data)); } } - function itemTemplate4( - rf1: RenderFlags, item4: any, item3: any, item2: any, item1: any, item0: any, - myApp: MyApp) { - if (rf1 & RenderFlags.Create) { + function itemTemplate4(rf: RenderFlags, ctx: any) { + if (rf & 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)); + if (rf & RenderFlags.Update) { + const item4 = ctx.$implicit as any; + elementProperty(1, 'ngForOf', bind(item4.data)); } } - function itemTemplate5( - rf1: RenderFlags, item5: any, item4: any, item3: any, item2: any, item1: any, item0: any, - myApp: MyApp) { - if (rf1 & RenderFlags.Create) { + function itemTemplate5(rf: RenderFlags, ctx: any) { + if (rf & 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)); + if (rf & RenderFlags.Update) { + const item5 = ctx.$implicit as any; + elementProperty(1, 'ngForOf', bind(item5.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) { + function itemTemplate6(rf: RenderFlags, ctx: any) { + if (rf & 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)); + if (rf & RenderFlags.Update) { + const item6 = ctx.$implicit as any; + elementProperty(1, 'ngForOf', bind(item6.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) { + function itemTemplate7(rf: RenderFlags, ctx: any) { + if (rf & 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)); + if (rf & RenderFlags.Update) { + const item7 = ctx.$implicit as any; + elementProperty(1, 'ngForOf', bind(item7.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) { + function itemTemplate8(rf: RenderFlags, ctx: any) { + if (rf & 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, '' - ])); + if (rf & RenderFlags.Update) { + const value = ctx.$implicit; + const item7 = nextContext().$implicit; + const item6 = nextContext().$implicit; + const item5 = nextContext().$implicit; + const item4 = nextContext().$implicit; + const item3 = nextContext().$implicit; + const item2 = nextContext().$implicit; + const item1 = nextContext().$implicit; + const item0 = nextContext().$implicit; + const myApp = nextContext(); + textBinding(1, interpolationV([ + '', value, '.', item7.value, '.', item6.value, '.', item5.value, + '.', item4.value, '.', item3.value, '.', item2.value, '.', item1.value, + '.', item0.value, '.', myApp.value, '' + ])); } } @@ -554,14 +730,14 @@ describe('@angular/common integration', () => { *
{{ valueOne }}
*
{{ valueTwo }}
*/ - template: (rf: RenderFlags, myApp: MyApp) => { + template: (rf: RenderFlags, ctx: MyApp) => { if (rf & RenderFlags.Create) { container(0, templateOne, undefined, ['ngIf', '']); container(1, templateTwo, undefined, ['ngIf', '']); } if (rf & RenderFlags.Update) { - elementProperty(0, 'ngIf', bind(myApp.showing)); - elementProperty(1, 'ngIf', bind(myApp.showing)); + elementProperty(0, 'ngIf', bind(ctx.showing)); + elementProperty(1, 'ngIf', bind(ctx.showing)); } }, @@ -569,24 +745,26 @@ describe('@angular/common integration', () => { }); } - function templateOne(rf: RenderFlags, ctx: any, myApp: MyApp) { + function templateOne(rf: RenderFlags, ctx: any) { if (rf & RenderFlags.Create) { elementStart(0, 'div'); { text(1); } elementEnd(); } if (rf & RenderFlags.Update) { + const myApp = nextContext(); textBinding(1, bind(myApp.valueOne)); } } - function templateTwo(rf: RenderFlags, ctx: any, myApp: MyApp) { + function templateTwo(rf: RenderFlags, ctx: any) { if (rf & RenderFlags.Create) { elementStart(0, 'div'); { text(1); } elementEnd(); } if (rf & RenderFlags.Update) { + const myApp = nextContext(); textBinding(1, bind(myApp.valueTwo)); } } @@ -600,6 +778,83 @@ describe('@angular/common integration', () => { expect(fixture.html).toEqual('
$$one$$
$$two$$
'); }); + it('should handle nested ngIfs with no intermediate context vars', () => { + /** + *
+ *
+ *