fix(ivy): convert context code into a tree-shakable instruction (#24943)

PR Close #24943
This commit is contained in:
Kara Erickson 2018-07-25 17:25:22 -07:00 committed by Igor Minar
parent fe14f180a6
commit 2ef777b0b2
24 changed files with 1058 additions and 519 deletions

View File

@ -1 +1,3 @@
Tests in this directory are excluded from running in the browser and only run in node. Tests in this directory should be run with:
bazel test --define=compile=local packages/compiler-cli/test/compliance:compliance

View File

@ -541,15 +541,16 @@ describe('compiler compliance', () => {
const MyComponentDefinition = ` const MyComponentDefinition = `
const $c1$ = ["foo", ""]; const $c1$ = ["foo", ""];
const $c2$ = ["if", ""]; const $c2$ = ["if", ""];
function MyComponent_li_Template_2(rf, ctx0, ctx) { function MyComponent_li_Template_2(rf, ctx) {
if (rf & 1) { if (rf & 1) {
$r3$.ɵE(0, "li"); $r3$.ɵE(0, "li");
$r3$.ɵT(1); $r3$.ɵT(1);
$r3$.ɵe(); $r3$.ɵe();
} }
if (rf & 2) { if (rf & 2) {
const $foo$ = $r3$.ɵr(1, 1); const $myComp$ = $r3$.ɵx();
$r3$.ɵt(1, $r3$.ɵi2("", ctx.salutation, " ", $foo$, "")); const $foo$ = $r3$.ɵr(1);
$r3$.ɵt(1, $r3$.ɵi2("", $myComp$.salutation, " ", $foo$, ""));
} }
} }
@ -1174,7 +1175,7 @@ describe('compiler compliance', () => {
$r3$.ɵT(2); $r3$.ɵT(2);
} }
if (rf & 2) { if (rf & 2) {
const $user$ = $r3$.ɵld(1); const $user$ = $r3$.ɵr(1);
$r3$.ɵt(2, $r3$.ɵi1("Hello ", $user$.value, "!")); $r3$.ɵt(2, $r3$.ɵi1("Hello ", $user$.value, "!"));
} }
} }
@ -1224,20 +1225,22 @@ describe('compiler compliance', () => {
const $c2$ = ["if", ""]; const $c2$ = ["if", ""];
const $c3$ = ["baz", ""]; const $c3$ = ["baz", ""];
const $c4$ = ["bar", ""]; const $c4$ = ["bar", ""];
function MyComponent_div_span_Template_2(rf, ctx1, ctx0, ctx) { function MyComponent_div_span_Template_2(rf, ctx) {
if (rf & 1) { if (rf & 1) {
$r3$.ɵE(0, "span"); $r3$.ɵE(0, "span");
$r3$.ɵT(1); $r3$.ɵT(1);
$r3$.ɵe(); $r3$.ɵe();
} }
if (rf & 2) { if (rf & 2) {
const $foo$ = $r3$.ɵr(2, 1); $r3$.ɵx();
const $bar$ = $r3$.ɵr(1, 4); const $bar$ = $r3$.ɵr(4);
const $baz$ = $r3$.ɵr(2, 5); $r3$.ɵx();
const $foo$ = $r3$.ɵr(1);
const $baz$ = $r3$.ɵr(5);
$r3$.ɵt(1, $r3$.ɵi3("", $foo$, "-", $bar$, "-", $baz$, "")); $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) { if (rf & 1) {
$r3$.ɵE(0, "div"); $r3$.ɵE(0, "div");
$r3$.ɵT(1); $r3$.ɵT(1);
@ -1246,8 +1249,9 @@ describe('compiler compliance', () => {
$r3$.ɵe(); $r3$.ɵe();
} }
if (rf & 2) { if (rf & 2) {
const $foo$ = $r3$.ɵr(1, 1); const $bar$ = $r3$.ɵr(4);
const $bar$ = $r3$.ɵld(4); $r3$.ɵx();
const $foo$ = $r3$.ɵr(1);
$r3$.ɵt(1, $r3$.ɵi2(" ", $foo$, "-", $bar$, " ")); $r3$.ɵt(1, $r3$.ɵi2(" ", $foo$, "-", $bar$, " "));
} }
} }
@ -1264,7 +1268,7 @@ describe('compiler compliance', () => {
$r3$.ɵEe(4, "div", null, $c3$); $r3$.ɵEe(4, "div", null, $c3$);
} }
if (rf & 2) { if (rf & 2) {
const $foo$ = $r3$.ɵld(1); const $foo$ = $r3$.ɵr(1);
$r3$.ɵt(2, $r3$.ɵi1(" ", $foo$, " ")); $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: \`
<div *ngFor="let item of items">
<div #foo></div>
<span *ngIf="showing">
{{ foo }} - {{ item }}
</span>
</div>\`
})
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', () => { describe('lifecycle hooks', () => {
const files = { const files = {
app: { app: {
@ -1452,7 +1527,7 @@ describe('compiler compliance', () => {
const MyComponentDefinition = ` const MyComponentDefinition = `
const $_c0$ = ["for","","forOf",""]; const $_c0$ = ["for","","forOf",""];
function MyComponent__svg_g_Template_1(rf, ctx0, ctx) { function MyComponent__svg_g_Template_1(rf, ctx) {
if (rf & 1) { if (rf & 1) {
$r3$.ɵNS(); $r3$.ɵNS();
$r3$.ɵE(0,"g"); $r3$.ɵE(0,"g");
@ -1525,14 +1600,14 @@ describe('compiler compliance', () => {
const MyComponentDefinition = ` const MyComponentDefinition = `
const $_c0$ = ["for","","forOf",""]; const $_c0$ = ["for","","forOf",""];
function MyComponent_li_Template_1(rf, ctx0, ctx) { function MyComponent_li_Template_1(rf, ctx) {
if (rf & 1) { if (rf & 1) {
$r3$.ɵE(0, "li"); $r3$.ɵE(0, "li");
$r3$.ɵT(1); $r3$.ɵT(1);
$r3$.ɵe(); $r3$.ɵe();
} }
if (rf & 2) { if (rf & 2) {
const $item$ = ctx0.$implicit; const $item$ = ctx.$implicit;
$r3$.ɵt(1, $r3$.ɵi1("", $item$.name, "")); $r3$.ɵt(1, $r3$.ɵi1("", $item$.name, ""));
} }
} }
@ -1602,20 +1677,20 @@ describe('compiler compliance', () => {
const MyComponentDefinition = ` const MyComponentDefinition = `
const $c1$ = ["for", "", "forOf", ""]; 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) { if (rf & 1) {
$r3$.ɵE(0, "li"); $r3$.ɵE(0, "li");
$r3$.ɵT(1); $r3$.ɵT(1);
$r3$.ɵe(); $r3$.ɵe();
} }
if (rf & 2) { if (rf & 2) {
const $item$ = ctx0.$implicit; const $info$ = ctx.$implicit;
const $info$ = ctx1.$implicit; const $item$ = $r3$.ɵx().$implicit;
$r3$.ɵt(1, $r3$.ɵi2(" ", $item$.name, ": ", $info$.description, " ")); $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) { if (rf & 1) {
$r3$.ɵE(0, "li"); $r3$.ɵE(0, "li");
$r3$.ɵE(1, "div"); $r3$.ɵE(1, "div");
@ -1627,7 +1702,7 @@ describe('compiler compliance', () => {
$r3$.ɵe(); $r3$.ɵe();
} }
if (rf & 2) { if (rf & 2) {
const $item$ = ctx0.$implicit; const $item$ = ctx.$implicit;
$r3$.ɵt(2, $r3$.ɵi1("", IDENT.name, "")); $r3$.ɵt(2, $r3$.ɵi1("", IDENT.name, ""));
$r3$.ɵp(4, "forOf", $r3$.ɵb(IDENT.infos)); $r3$.ɵp(4, "forOf", $r3$.ɵb(IDENT.infos));
} }

View File

@ -52,56 +52,262 @@ describe('compiler compliance: template', () => {
// The template should look like this (where IDENT is a wild card for an identifier): // The template should look like this (where IDENT is a wild card for an identifier):
const template = ` const template = `
const $c0$ = ["ngFor","","ngForOf",""]; 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) { 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$.ɵE(0, "div");
$i0$.ɵL("click", function MyComponent_ul_li_div_Template_1_div_click_listener($event){ $i0$.ɵL("click", function MyComponent_ul_li_div_Template_1_div_click_listener($event){
const $outer$ = $ctx0$.$implicit; return $myComp$.onClick($outer$, $middle$, $inner$);
const $middle$ = $ctx1$.$implicit;
const $inner$ = $ctx2$.$implicit;
return ctx.onClick($outer$, $middle$, $inner$);
}); });
$i0$.ɵT(1); $i0$.ɵT(1);
$i0$.ɵe(); $i0$.ɵe();
} }
if (rf & 2) { if (rf & 2) {
const $outer$ = $ctx0$.$implicit; const $inner1$ = ctx.$implicit;
const $middle$ = $ctx1$.$implicit; const $middle1$ = $i0$.ɵx().$implicit;
const $inner$ = $ctx2$.$implicit; const $outer1$ = $i0$.ɵx().$implicit;
$i0$.ɵp(0, "title", $i0$.ɵb(ctx.format($outer$, $middle$, $inner$, $ctx$.component))); const $myComp1$ = $i0$.ɵx();
$i0$.ɵt(1, $i0$.ɵi1(" ", ctx.format($outer$, $middle$, $inner$, $ctx$.component), " ")); $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) { if (rf & 1) {
$i0$.ɵE(0, "li"); $i0$.ɵE(0, "li");
$i0$.ɵC(1, MyComponent_ul_li_div_Template_1, null, _c0); $i0$.ɵC(1, MyComponent_ul_li_div_Template_1, null, _c0);
$i0$.ɵe(); $i0$.ɵe();
} }
if (rf & 2) { 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) { if (rf & 1) {
$i0$.ɵE(0, "ul"); $i0$.ɵE(0, "ul");
$i0$.ɵC(1, MyComponent_ul_li_Template_1, null, _c0); $i0$.ɵC(1, MyComponent_ul_li_Template_1, null, _c0);
$i0$.ɵe(); $i0$.ɵe();
} }
if (rf & 2) { if (rf & 2) {
const $outer$ = $ctx0$.$implicit; const $outer2$ = ctx.$implicit;
$i0$.ɵp(1, "ngForOf", $i0$.ɵb($outer$.items)); $i0$.ɵp(1, "ngForOf", $i0$.ɵb($outer2$.items));
} }
} }
// ... // ...
template:function MyComponent_Template(rf, $ctx$){ template:function MyComponent_Template(rf, ctx){
if (rf & 1) { if (rf & 1) {
$i0$.ɵC(0, MyComponent_ul_Template_0, null, _c0); $i0$.ɵC(0, MyComponent_ul_Template_0, null, _c0);
} }
if (rf & 2) { 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: \`
<span *ngFor="let item of items; index as i">
{{ i }} - {{ item }}
</span>\`
})
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: \`
<div *ngFor="let item of items; index as i">
<span *ngIf="showing">
{{ i }} - {{ item }}
</span>
</div>\`
})
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: \`
<div *ngFor="let outer of items">
<div *ngFor="let middle of outer.items">
<div *ngFor="let inner of middle.items">
{{ middle.value }} - {{ name }}
</div>
</div>
</div>\`
})
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));
} }
}`; }`;

View File

@ -338,6 +338,8 @@ export class WriteVarExpr extends Expression {
toDeclStmt(type?: Type|null, modifiers?: StmtModifier[]|null): DeclareVarStmt { toDeclStmt(type?: Type|null, modifiers?: StmtModifier[]|null): DeclareVarStmt {
return new DeclareVarStmt(this.name, this.value, type, modifiers, this.sourceSpan); return new DeclareVarStmt(this.name, this.value, type, modifiers, this.sourceSpan);
} }
toConstDecl(): DeclareVarStmt { return this.toDeclStmt(INFERRED_TYPE, [StmtModifier.Final]); }
} }

View File

@ -45,6 +45,8 @@ export class Identifiers {
static containerCreate: o.ExternalReference = {name: 'ɵC', moduleName: CORE}; 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 text: o.ExternalReference = {name: 'ɵT', moduleName: CORE};
static textBinding: o.ExternalReference = {name: 'ɵt', moduleName: CORE}; static textBinding: o.ExternalReference = {name: 'ɵt', moduleName: CORE};

View File

@ -149,7 +149,7 @@ export function compileComponentFromMetadata(
const template = meta.template; const template = meta.template;
const templateFunctionExpression = const templateFunctionExpression =
new TemplateDefinitionBuilder( 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, meta.viewQueries, directiveMatcher, directivesUsed, meta.pipes, pipesUsed,
R3.namespaceHTML) R3.namespaceHTML)
.buildTemplateFunction( .buildTemplateFunction(

View File

@ -57,12 +57,33 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
private _bindingContext = 0; private _bindingContext = 0;
private _prefixCode: o.Statement[] = []; private _prefixCode: o.Statement[] = [];
private _creationCode: o.Statement[] = []; private _creationCode: o.Statement[] = [];
private _variableCode: o.Statement[] = []; /**
private _bindingCode: (() => o.Statement)[] = []; * List of callbacks to generate update mode instructions. We store them here as we process
private _nestedTemplates: (() => void)[] = []; * 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 _valueConverter: ValueConverter;
private _unsupported = unsupported; private _unsupported = unsupported;
private _bindingScope: BindingScope;
// Whether we are inside a translatable element (`<p i18n>... somewhere here ... </p>) // Whether we are inside a translatable element (`<p i18n>... somewhere here ... </p>)
private _inI18nSection: boolean = false; private _inI18nSection: boolean = false;
@ -74,20 +95,19 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
private _pureFunctionSlots = 0; private _pureFunctionSlots = 0;
constructor( constructor(
private constantPool: ConstantPool, private contextParameter: string, private constantPool: ConstantPool, parentBindingScope: BindingScope, private level = 0,
parentBindingScope: BindingScope, private level = 0, private contextName: string|null, private contextName: string|null, private templateName: string|null,
private templateName: string|null, private viewQueries: R3QueryMetadata[], private viewQueries: R3QueryMetadata[], private directiveMatcher: SelectorMatcher|null,
private directiveMatcher: SelectorMatcher|null, private directives: Set<o.Expression>, private directives: Set<o.Expression>, private pipeTypeByName: Map<string, o.Expression>,
private pipeTypeByName: Map<string, o.Expression>, private pipes: Set<o.Expression>, private pipes: Set<o.Expression>, private _namespace: o.ExternalReference) {
private _namespace: o.ExternalReference) {
// view queries can take up space in data and allocation happens earlier (in the "viewQuery" // view queries can take up space in data and allocation happens earlier (in the "viewQuery"
// function) // function)
this._dataIndex = viewQueries.length; this._dataIndex = viewQueries.length;
this._bindingScope =
parentBindingScope.nestedScope(level, (lhsVar: o.ReadVarExpr, rhsExpr: o.Expression) => { // TODO(kara): generate restore instruction in listener to replace creation scope
this._variableCode.push( this._creationScope = parentBindingScope.nestedScope(level);
lhsVar.set(rhsExpr).toDeclStmt(o.INFERRED_TYPE, [o.StmtModifier.Final])); this._updateScope = parentBindingScope.nestedScope(level);
});
this._valueConverter = new ValueConverter( this._valueConverter = new ValueConverter(
constantPool, () => this.allocateDataSlot(), constantPool, () => this.allocateDataSlot(),
(numSlots: number): number => this._pureFunctionSlots += numSlots, (numSlots: number): number => this._pureFunctionSlots += numSlots,
@ -96,12 +116,33 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
if (pipeType) { if (pipeType) {
this.pipes.add(pipeType); this.pipes.add(pipeType);
} }
this._bindingScope.set(localName, value); this._updateScope.set(this.level, localName, value);
this._creationCode.push( this._creationCode.push(
o.importExpr(R3.pipe).callFn([o.literal(slot), o.literal(name)]).toStmt()); 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( buildTemplateFunction(
nodes: t.Node[], variables: t.Variable[], hasNgContent: boolean = false, nodes: t.Node[], variables: t.Variable[], hasNgContent: boolean = false,
ngContentSelectors: string[] = []): o.FunctionExpr { ngContentSelectors: string[] = []): o.FunctionExpr {
@ -111,12 +152,9 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
// Create variable bindings // Create variable bindings
for (const variable of variables) { 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. // 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 `<ng-content>` are present // Output a `ProjectionDef` instruction when some `<ng-content>` are present
@ -135,42 +173,50 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
this.creationInstruction(null, R3.projectionDef, ...parameters); 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); t.visitAll(this, nodes);
const creationCode = this._creationCode.length > 0 ? // Generate all the update mode instructions as the second pass (e.g. resolve bindings)
[renderFlagCheckIfStmt(core.RenderFlags.Create, this._creationCode)] : const updateStatements = this._updateCodeFns.map((fn: () => o.Statement) => fn());
[];
const updateCode = this._bindingCode.length > 0 ?
[renderFlagCheckIfStmt(core.RenderFlags.Update, this._variableCode.concat(
this._variableCode.concat(this._bindingCode.map((fn: () => o.Statement) => fn()))))] :
[];
// To count slots for the reserveSlots() instruction, all bindings must have been visited.
if (this._pureFunctionSlots > 0) { if (this._pureFunctionSlots > 0) {
this.creationInstruction(null, R3.reserveSlots, o.literal(this._pureFunctionSlots)); 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 // Generate maps of placeholder name to node indexes
// TODO(vicb): This is a WIP, not fully supported yet // TODO(vicb): This is a WIP, not fully supported yet
for (const phToNodeIdx of this._phToNodeIdxes) { for (const phToNodeIdx of this._phToNodeIdxes) {
if (Object.keys(phToNodeIdx).length > 0) { if (Object.keys(phToNodeIdx).length > 0) {
const scopedName = this._bindingScope.freshReferenceName(); const scopedName = this._updateScope.freshReferenceName();
const phMap = o.variable(scopedName) const phMap = o.variable(scopedName).set(mapToExpression(phToNodeIdx, true)).toConstDecl();
.set(mapToExpression(phToNodeIdx, true))
.toDeclStmt(o.INFERRED_TYPE, [o.StmtModifier.Final]);
this._prefixCode.push(phMap); this._prefixCode.push(phMap);
} }
} }
this._nestedTemplates.forEach(buildTemplateFn => buildTemplateFn()); this._nestedTemplateFns.forEach(buildTemplateFn => buildTemplateFn());
return o.fn( return o.fn(
// i.e. (rf: RenderFlags, ctx0: any, ctx: any) // i.e. (rf: RenderFlags, ctx: any)
[ [new o.FnParam(RENDER_FLAGS, o.NUMBER_TYPE), new o.FnParam(CONTEXT_NAME, null)],
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;) // Temporary variable declarations for query refresh (i.e. let _t: any;)
...this._prefixCode, ...this._prefixCode,
@ -183,7 +229,7 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
} }
// 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) { visitContent(ngContent: t.Content) {
const slot = this.allocateDataSlot(); const slot = this.allocateDataSlot();
@ -225,18 +271,6 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
this.creationInstruction(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) { visitElement(element: t.Element) {
const elementIndex = this.allocateDataSlot(); const elementIndex = this.allocateDataSlot();
const wasInI18nSection = this._inI18nSection; const wasInI18nSection = this._inI18nSection;
@ -426,17 +460,19 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
const references = flatten(element.references.map(reference => { const references = flatten(element.references.map(reference => {
const slot = this.allocateDataSlot(); const slot = this.allocateDataSlot();
// Generate the update temporary. // Generate the update temporary.
const variableName = this._bindingScope.freshReferenceName(); const variableName = this._updateScope.freshReferenceName();
// When the ref's binding is processed, we'll either generate a load() or a reference() const retrievalLevel = this.level;
// instruction depending on the nesting level of the binding relative to the reference def. const lhs = o.variable(variableName);
const refLevel = this.level; this._updateScope.set(
this._bindingScope.set( retrievalLevel, reference.name, lhs, DeclarationPriority.DEFAULT,
reference.name, o.variable(variableName), undefined, (bindingLevel: number) => { (scope: BindingScope, relativeLevel: number) => {
return bindingLevel === refLevel ? // e.g. x(2);
o.importExpr(R3.load).callFn([o.literal(slot)]) : const nextContextStmt =
o.importExpr(R3.reference).callFn([ relativeLevel > 0 ? [generateNextContextExpr(relativeLevel).toStmt()] : [];
o.literal(bindingLevel - refLevel), o.literal(slot)
]); // e.g. const $foo$ = r(1);
const refExpr = lhs.set(o.importExpr(R3.reference).callFn([o.literal(slot)]));
return nextContextStmt.concat(refExpr.toConstDecl());
}); });
return [reference.name, reference.value]; return [reference.name, reference.value];
})); }));
@ -515,17 +551,11 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
const elName = sanitizeIdentifier(element.name); const elName = sanitizeIdentifier(element.name);
const evName = sanitizeIdentifier(outputAst.name); const evName = sanitizeIdentifier(outputAst.name);
const functionName = `${this.templateName}_${elName}_${evName}_listener`; 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( const bindingExpr = convertActionBinding(
bindingScope, implicit, outputAst.handler, 'b', this._creationScope, implicit, outputAst.handler, 'b',
() => error('Unexpected interpolation')); () => error('Unexpected interpolation'));
const handler = o.fn( 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); o.INFERRED_TYPE, null, functionName);
this.creationInstruction( this.creationInstruction(
outputAst.sourceSpan, R3.listener, o.literal(outputAst.name), handler); outputAst.sourceSpan, R3.listener, o.literal(outputAst.name), handler);
@ -668,8 +698,6 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
const templateName = const templateName =
contextName ? `${contextName}_Template_${templateIndex}` : `Template_${templateIndex}`; contextName ? `${contextName}_Template_${templateIndex}` : `Template_${templateIndex}`;
const templateContext = `ctx${this.level}`;
const parameters: o.Expression[] = [ const parameters: o.Expression[] = [
o.literal(templateIndex), o.literal(templateIndex),
o.variable(templateName), o.variable(templateName),
@ -713,15 +741,14 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
// Create the template function // Create the template function
const templateVisitor = new TemplateDefinitionBuilder( const templateVisitor = new TemplateDefinitionBuilder(
this.constantPool, templateContext, this._bindingScope, this.level + 1, contextName, this.constantPool, this._updateScope, this.level + 1, contextName, templateName, [],
templateName, [], this.directiveMatcher, this.directives, this.pipeTypeByName, this.pipes, this.directiveMatcher, this.directives, this.pipeTypeByName, this.pipes, this._namespace);
this._namespace);
// Nested templates must not be visited until after their parent templates have completed // 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 // 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 // be able to support bindings in nested templates to local refs that occur after the
// template definition. e.g. <div *ngIf="showing"> {{ foo }} </div> <div #foo></div> // template definition. e.g. <div *ngIf="showing"> {{ foo }} </div> <div #foo></div>
this._nestedTemplates.push(() => { this._nestedTemplateFns.push(() => {
const templateFunctionExpr = const templateFunctionExpr =
templateVisitor.buildTemplateFunction(template.children, template.variables); templateVisitor.buildTemplateFunction(template.children, template.variables);
this.constantPool.statements.push(templateFunctionExpr.toDeclStmt(templateName, null)); this.constantPool.statements.push(templateFunctionExpr.toDeclStmt(templateName, null));
@ -790,7 +817,7 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
// bindings. e.g. {{ foo }} <div #foo></div> // bindings. e.g. {{ foo }} <div #foo></div>
private updateInstruction( private updateInstruction(
span: ParseSourceSpan|null, reference: o.ExternalReference, paramsFn: () => o.Expression[]) { 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): private convertPropertyBinding(implicit: o.Expression, value: AST, skipBindFn?: boolean):
@ -800,7 +827,7 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
const convertedPropertyBinding = convertPropertyBinding( const convertedPropertyBinding = convertPropertyBinding(
this, implicit, value, this.bindingContext(), BindingForm.TrySimple, interpolationFn); this, implicit, value, this.bindingContext(), BindingForm.TrySimple, interpolationFn);
this._variableCode.push(...convertedPropertyBinding.stmts); this._tempVariables.push(...convertedPropertyBinding.stmts);
const valExpr = convertedPropertyBinding.currValExpr; const valExpr = convertedPropertyBinding.currValExpr;
return value instanceof Interpolation || skipBindFn ? valExpr : 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( function getLiteralFactory(
constantPool: ConstantPool, literal: o.LiteralArrayExpr | o.LiteralMapExpr, constantPool: ConstantPool, literal: o.LiteralArrayExpr | o.LiteralMapExpr,
allocateSlots: (numSlots: number) => number): o.Expression { allocateSlots: (numSlots: number) => number): o.Expression {
@ -919,33 +952,44 @@ function getLiteralFactory(
* *
* It is expected that the function creates the `const localName = expression`; statement. * 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 { export class BindingScope implements LocalResolver {
/** /** Keeps a map from local variables to their BindingData. */
* Keeps a map from local variables to their expressions. private map = new Map<string, BindingData>();
*
* 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;
}
> ();
private referenceNameIndex = 0; 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 constructor(public bindingLevel: number = 0, private parent: BindingScope|null = null) {}
private level: number = 0, private parent: BindingScope|null = null,
private declareLocalVarCallback: DeclareLocalVarCallback = noop) {}
get(name: string): o.Expression|null { get(name: string): o.Expression|null {
let current: BindingScope|null = this; let current: BindingScope|null = this;
@ -953,48 +997,118 @@ export class BindingScope implements LocalResolver {
let value = current.map.get(name); let value = current.map.get(name);
if (value != null) { if (value != null) {
if (current !== this) { if (current !== this) {
// make a local copy and reset the `declared` state. // make a local copy and reset the `declare` state
value = {lhs: value.lhs, rhs: value.rhs, rhsCallback: value.rhsCallback, declared: false}; value = {
retrievalLevel: value.retrievalLevel,
lhs: value.lhs,
declareLocalCallback: value.declareLocalCallback,
declare: false,
priority: value.priority
};
// Cache the value locally. // Cache the value locally.
this.map.set(name, value); 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 (value.declareLocalCallback && !value.declare) {
// if it is first time we are referencing the variable in the scope value.declare = true;
// then invoke the callback to insert variable declaration.
this.declareLocalVarCallback(value.lhs, rhs);
value.declared = true;
} }
return value.lhs; return value.lhs;
} }
current = current.parent; 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. * 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 name Name of the variable.
* @param lhs AST representing the left hand side of the `let lhs = rhs;`. * @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 * @param priority The sorting priority of this var
* `undefined` for variable that are ambient such as `$event` and which don't have `rhs` * @param declareLocalCallback The callback to invoke when declaring this local var
* declaration.
*/ */
set(name: string, lhs: o.ReadVarExpr, rhs?: o.Expression, set(retrievalLevel: number, name: string, lhs: o.ReadVarExpr,
rhsCallback?: (level: number) => o.Expression): BindingScope { priority: number = DeclarationPriority.DEFAULT,
declareLocalCallback?: DeclareLocalVarCallback): BindingScope {
!this.map.has(name) || !this.map.has(name) ||
error(`The name ${name} is already defined in scope to be ${this.map.get(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; return this;
} }
getLocal(name: string): (o.Expression|null) { return this.get(name); } getLocal(name: string): (o.Expression|null) { return this.get(name); }
nestedScope(level: number, declareCallback: DeclareLocalVarCallback): BindingScope { nestedScope(level: number): BindingScope {
return new BindingScope(level, this, declareCallback); 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 { freshReferenceName(): string {
let current: BindingScope = this; let current: BindingScope = this;
// Find the top scope as it maintains the global reference count // Find the top scope as it maintains the global reference count

View File

@ -32,6 +32,7 @@ export {
NgModuleFactory as ɵNgModuleFactory, NgModuleFactory as ɵNgModuleFactory,
NC as ɵNC, NC as ɵNC,
C as ɵC, C as ɵC,
x as ɵx,
E as ɵE, E as ɵE,
NH as ɵNH, NH as ɵNH,
NM as ɵNM, NM as ɵNM,

View File

@ -22,7 +22,7 @@ import {baseDirectiveCreate, createLNode, createLViewData, createTView, elementC
import {ComponentDefInternal, ComponentType, RenderFlags} from './interfaces/definition'; import {ComponentDefInternal, ComponentType, RenderFlags} from './interfaces/definition';
import {LElementNode, TNode, TNodeType} from './interfaces/node'; import {LElementNode, TNode, TNodeType} from './interfaces/node';
import {RElement, domRendererFactory3} from './interfaces/renderer'; 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'; import {ViewRef} from './view_ref';
export class ComponentFactoryResolver extends viewEngine_ComponentFactoryResolver { export class ComponentFactoryResolver extends viewEngine_ComponentFactoryResolver {
@ -120,6 +120,7 @@ export class ComponentFactory<T> extends viewEngine_ComponentFactory<T> {
rootContext.components.push( rootContext.components.push(
component = baseDirectiveCreate(0, this.componentDef.factory(), this.componentDef) as T); component = baseDirectiveCreate(0, this.componentDef.factory(), this.componentDef) as T);
initChangeDetectorIfExisting(elementNode.nodeInjector, component, elementNode.data !); 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 // TODO: should LifecycleHooksFeature and other host features be generated by the compiler and
// executed here? // executed here?

View File

@ -738,8 +738,8 @@ class TemplateRef<T> implements viewEngine.TemplateRef<T> {
readonly elementRef: viewEngine.ElementRef; readonly elementRef: viewEngine.ElementRef;
constructor( constructor(
private _declarationParentView: LViewData, elementRef: viewEngine.ElementRef, private _tView: TView, private _renderer: Renderer3, private _declarationParentView: LViewData, elementRef: viewEngine.ElementRef,
private _queries: LQueries|null) { private _tView: TView, private _renderer: Renderer3, private _queries: LQueries|null) {
this.elementRef = elementRef; this.elementRef = elementRef;
} }

View File

@ -49,6 +49,8 @@ export {
containerRefreshStart as cR, containerRefreshStart as cR,
containerRefreshEnd as cr, containerRefreshEnd as cr,
nextContext as x,
element as Ee, element as Ee,
elementAttribute as a, elementAttribute as a,
elementClassProp as cp, elementClassProp as cp,

View File

@ -16,7 +16,7 @@ import {assertDefined, assertEqual, assertLessThan, assertNotDefined, assertNotE
import {throwCyclicDependencyError, throwErrorIfNoChangesMode, throwMultipleComponentError} from './errors'; import {throwCyclicDependencyError, throwErrorIfNoChangesMode, throwMultipleComponentError} from './errors';
import {executeHooks, executeInitHooks, queueInitHooks, queueLifecycleHooks} from './hooks'; import {executeHooks, executeInitHooks, queueInitHooks, queueLifecycleHooks} from './hooks';
import {ACTIVE_INDEX, LContainer, RENDER_PARENT, VIEWS} from './interfaces/container'; 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 {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 {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 {CssSelectorList, NG_PROJECT_AS_ATTR_NAME} from './interfaces/projection';
@ -165,6 +165,14 @@ export function getCreationMode(): boolean {
*/ */
let viewData: LViewData; 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. * An array of directive instances in the current view.
* *
@ -223,7 +231,7 @@ export function enterView(newView: LViewData, host: LElementNode | LViewNode | n
isParent = true; isParent = true;
} }
viewData = newView; viewData = contextViewData = newView;
currentQueries = newView && newView[QUERIES]; currentQueries = newView && newView[QUERIES];
return oldView; return oldView;
@ -547,7 +555,7 @@ export function renderEmbeddedTemplate<T>(
oldView = enterView(viewNode.data !, viewNode); oldView = enterView(viewNode.data !, viewNode);
namespaceHTML(); namespaceHTML();
callTemplateWithContexts(rf, context, tView.template !, viewNode.data ![DECLARATION_VIEW] !); tView.template !(rf, context);
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
refreshDescendantViews(); refreshDescendantViews();
} else { } else {
@ -566,113 +574,18 @@ export function renderEmbeddedTemplate<T>(
} }
/** /**
* This function calls the template function of a dynamically created view with * Retrieves a context at the level specified and saves it as the global, contextViewData.
* all of its declaration parent contexts (up the view tree) until it reaches the * Will get the next level up if level is not specified.
* component boundary.
* *
* 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: * @param level The relative level of the view from which to grab context compared to contextVewData
* <ul *ngFor="let list of lists"> * @returns context
* <li *ngFor="let item of list"> {{ item }} </li>
* </ul>
*
* 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
*/ */
function callTemplateWithContexts<T>( export function nextContext(level: number = 1): any {
rf: RenderFlags, currentContext: T, template: EmbeddedTemplate<T>, contextViewData = walkUpViews(level, contextViewData !);
parentView: LViewData): void { return contextViewData[CONTEXT];
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 renderComponentOrTemplate<T>( export function renderComponentOrTemplate<T>(
@ -1007,7 +920,7 @@ function getOrCreateTView(
* @param pipes Registry of pipes for this view * @param pipes Registry of pipes for this view
*/ */
export function createTView( export function createTView(
viewIndex: number, template: ComponentTemplate<any>| EmbeddedTemplate<any>| null, viewIndex: number, template: ComponentTemplate<any>| null,
directives: DirectiveDefListOrFactory | null, pipes: PipeDefListOrFactory | null, directives: DirectiveDefListOrFactory | null, pipes: PipeDefListOrFactory | null,
viewQuery: ComponentQuery<any>| null): TView { viewQuery: ComponentQuery<any>| null): TView {
ngDevMode && ngDevMode.tView++; ngDevMode && ngDevMode.tView++;
@ -1828,7 +1741,7 @@ export function createLContainer(
* @param localRefs A set of local reference bindings on the element. * @param localRefs A set of local reference bindings on the element.
*/ */
export function container( export function container(
index: number, template?: EmbeddedTemplate<any>, tagName?: string | null, attrs?: TAttributes, index: number, template?: ComponentTemplate<any>, tagName?: string | null, attrs?: TAttributes,
localRefs?: string[] | null): void { localRefs?: string[] | null): void {
ngDevMode && ngDevMode &&
assertEqual( assertEqual(
@ -2653,9 +2566,19 @@ export function store<T>(index: number, value: T): void {
viewData[adjustedIndex] = value; viewData[adjustedIndex] = value;
} }
/** Retrieves a value from an LViewData at the given nesting level. */ /**
export function reference<T>(nestingLevel: number, index: number) { * Retrieves a local reference from the current contextViewData.
let currentView = viewData; *
* 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<T>(index: number) {
return loadInternal<T>(index, contextViewData);
}
function walkUpViews(nestingLevel: number, currentView: LViewData): LViewData {
while (nestingLevel > 0) { while (nestingLevel > 0) {
ngDevMode && assertDefined( ngDevMode && assertDefined(
currentView[DECLARATION_VIEW], currentView[DECLARATION_VIEW],
@ -2663,8 +2586,7 @@ export function reference<T>(nestingLevel: number, index: number) {
currentView = currentView[DECLARATION_VIEW] !; currentView = currentView[DECLARATION_VIEW] !;
nestingLevel--; nestingLevel--;
} }
return currentView;
return loadInternal<T>(index, currentView);
} }
/** Retrieves a value from the `directives` array. */ /** Retrieves a value from the `directives` array. */

View File

@ -19,13 +19,6 @@ export type ComponentTemplate<T> = {
(rf: RenderFlags, ctx: T): 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<T> = {
(rf: RenderFlags, ctx: T, ...parentCtx: any[]): void;
};
/** /**
* Definition of what a query function should look like. * Definition of what a query function should look like.
*/ */

View File

@ -36,6 +36,7 @@ export const angularCoreEnv: {[name: string]: Function} = {
'ɵa': r3.a, 'ɵa': r3.a,
'ɵb': r3.b, 'ɵb': r3.b,
'ɵC': r3.C, 'ɵC': r3.C,
'ɵx': r3.x,
'ɵcR': r3.cR, 'ɵcR': r3.cR,
'ɵcr': r3.cr, 'ɵcr': r3.cr,
'ɵd': r3.d, 'ɵd': r3.d,

View File

@ -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 {EmbeddedViewRef as viewEngine_EmbeddedViewRef, InternalViewRef as viewEngine_InternalViewRef} from '../linker/view_ref';
import {checkNoChanges, detectChanges, markViewDirty, storeCleanupFn, viewAttached} from './instructions'; import {checkNoChanges, detectChanges, markViewDirty, storeCleanupFn, viewAttached} from './instructions';
import {EmbeddedTemplate} from './interfaces/definition';
import {LViewNode} from './interfaces/node'; import {LViewNode} from './interfaces/node';
import {FLAGS, LViewData, LViewFlags} from './interfaces/view'; import {FLAGS, LViewData, LViewFlags} from './interfaces/view';
import {destroyLView} from './node_manipulation'; import {destroyLView} from './node_manipulation';
@ -244,30 +243,3 @@ export class ViewRef<T> implements viewEngine_EmbeddedViewRef<T>, viewEngine_Int
attachToAppRef(appRef: ApplicationRef) { this._appRef = appRef; } attachToAppRef(appRef: ApplicationRef) { this._appRef = appRef; }
} }
<<<<<<< HEAD
=======
export class EmbeddedViewRef<T> extends ViewRef<T> {
/**
* @internal
*/
_lViewNode: LViewNode;
private _viewContainerRef: viewEngine_ViewContainerRef|null = null;
constructor(viewNode: LViewNode, template: EmbeddedTemplate<T>, 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

View File

@ -17,9 +17,6 @@
{ {
"name": "ChangeDetectionStrategy" "name": "ChangeDetectionStrategy"
}, },
{
"name": "DECLARATION_VIEW"
},
{ {
"name": "DIRECTIVES" "name": "DIRECTIVES"
}, },
@ -101,9 +98,6 @@
{ {
"name": "callHooks" "name": "callHooks"
}, },
{
"name": "callTemplateWithContexts"
},
{ {
"name": "canInsertNativeNode" "name": "canInsertNativeNode"
}, },

View File

@ -326,9 +326,6 @@
{ {
"name": "callHooks" "name": "callHooks"
}, },
{
"name": "callTemplateWithContexts"
},
{ {
"name": "canInsertNativeNode" "name": "canInsertNativeNode"
}, },
@ -347,6 +344,9 @@
{ {
"name": "container" "name": "container"
}, },
{
"name": "contextViewData"
},
{ {
"name": "createDirectivesAndLocals" "name": "createDirectivesAndLocals"
}, },
@ -692,6 +692,9 @@
{ {
"name": "namespaceHTML" "name": "namespaceHTML"
}, },
{
"name": "nextContext"
},
{ {
"name": "pointers" "name": "pointers"
}, },
@ -725,9 +728,6 @@
{ {
"name": "readElementValue" "name": "readElementValue"
}, },
{
"name": "reference"
},
{ {
"name": "refreshChildComponents" "name": "refreshChildComponents"
}, },
@ -869,6 +869,9 @@
{ {
"name": "walkLNodeTree" "name": "walkLNodeTree"
}, },
{
"name": "walkUpViews"
},
{ {
"name": "wrapListenerWithDirtyAndDefault" "name": "wrapListenerWithDirtyAndDefault"
}, },

View File

@ -10,7 +10,7 @@ import {NgForOfContext} from '@angular/common';
import {getOrCreateNodeInjectorForNode, getOrCreateTemplateRef} from '../../src/render3/di'; import {getOrCreateNodeInjectorForNode, getOrCreateTemplateRef} from '../../src/render3/di';
import {AttributeMarker, defineComponent} from '../../src/render3/index'; 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 {RenderFlags} from '../../src/render3/interfaces/definition';
import {NgForOf, NgIf, NgTemplateOutlet} from './common_with_def'; import {NgForOf, NgIf, NgTemplateOutlet} from './common_with_def';
@ -20,6 +20,18 @@ describe('@angular/common integration', () => {
describe('NgForOf', () => { describe('NgForOf', () => {
it('should update a loop', () => { it('should update a loop', () => {
function liTemplate(rf: RenderFlags, ctx: NgForOfContext<string>) {
if (rf & RenderFlags.Create) {
elementStart(0, 'li');
{ text(1); }
elementEnd();
}
if (rf & RenderFlags.Update) {
const item = ctx.$implicit;
textBinding(1, bind(item));
}
}
class MyApp { class MyApp {
items: string[] = ['first', 'second']; items: string[] = ['first', 'second'];
@ -30,25 +42,14 @@ describe('@angular/common integration', () => {
// <ul> // <ul>
// <li *ngFor="let item of items">{{item}}</li> // <li *ngFor="let item of items">{{item}}</li>
// </ul> // </ul>
template: (rf: RenderFlags, myApp: MyApp) => { template: (rf: RenderFlags, ctx: MyApp) => {
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
elementStart(0, 'ul'); elementStart(0, 'ul');
{ container(1, liTemplate, undefined, ['ngForOf', '']); } { container(1, liTemplate, undefined, ['ngForOf', '']); }
elementEnd(); elementEnd();
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
elementProperty(1, 'ngForOf', bind(myApp.items)); elementProperty(1, 'ngForOf', bind(ctx.items));
}
function liTemplate(rf1: RenderFlags, row: NgForOfContext<string>, parent: MyApp) {
if (rf1 & RenderFlags.Create) {
elementStart(0, 'li');
{ text(1); }
elementEnd();
}
if (rf1 & RenderFlags.Update) {
textBinding(1, bind(row.$implicit));
}
} }
}, },
directives: () => [NgForOf] directives: () => [NgForOf]
@ -79,6 +80,17 @@ describe('@angular/common integration', () => {
}); });
it('should support ngForOf context variables', () => { it('should support ngForOf context variables', () => {
function liTemplate(rf: RenderFlags, ctx: NgForOfContext<string>) {
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 { class MyApp {
items: string[] = ['first', 'second']; items: string[] = ['first', 'second'];
@ -88,29 +100,19 @@ describe('@angular/common integration', () => {
factory: () => new MyApp(), factory: () => new MyApp(),
selectors: [['my-app']], selectors: [['my-app']],
// <ul> // <ul>
// <li *ngFor="let item of items">{{index}} of {{count}}: {{item}}</li> // <li *ngFor="let item of items; index as index; count as count">{{index}} of
// {{count}}: {{item}}</li>
// </ul> // </ul>
template: (rf: RenderFlags, myApp: MyApp) => { template: (rf: RenderFlags, ctx: MyApp) => {
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
elementStart(0, 'ul'); elementStart(0, 'ul');
{ container(1, liTemplate, undefined, ['ngForOf', '']); } { container(1, liTemplate, undefined, ['ngForOf', '']); }
elementEnd(); elementEnd();
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
elementProperty(1, 'ngForOf', bind(myApp.items)); elementProperty(1, 'ngForOf', bind(ctx.items));
} }
function liTemplate(rf1: RenderFlags, row: NgForOfContext<string>, 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] directives: () => [NgForOf]
}); });
@ -128,6 +130,18 @@ describe('@angular/common integration', () => {
it('should retain parent view listeners when the NgFor destroy views', () => { it('should retain parent view listeners when the NgFor destroy views', () => {
function liTemplate(rf: RenderFlags, ctx: NgForOfContext<string>) {
if (rf & RenderFlags.Create) {
elementStart(0, 'li');
{ text(1); }
elementEnd();
}
if (rf & RenderFlags.Update) {
const item = ctx.$implicit;
textBinding(1, interpolation1('', item, ''));
}
}
class MyApp { class MyApp {
private _data: number[] = [1, 2, 3]; private _data: number[] = [1, 2, 3];
items: number[] = []; items: number[] = [];
@ -148,11 +162,11 @@ describe('@angular/common integration', () => {
// <ul> // <ul>
// <li *ngFor="let item of items">{{index}}</li> // <li *ngFor="let item of items">{{index}}</li>
// </ul> // </ul>
template: (rf: RenderFlags, myApp: MyApp) => { template: (rf: RenderFlags, ctx: MyApp) => {
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
elementStart(0, 'button'); elementStart(0, 'button');
{ {
listener('click', function() { return myApp.toggle(); }); listener('click', function() { return ctx.toggle(); });
text(1, 'Toggle List'); text(1, 'Toggle List');
} }
elementEnd(); elementEnd();
@ -161,19 +175,9 @@ describe('@angular/common integration', () => {
elementEnd(); elementEnd();
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
elementProperty(3, 'ngForOf', bind(myApp.items)); elementProperty(3, 'ngForOf', bind(ctx.items));
} }
function liTemplate(rf1: RenderFlags, row: NgForOfContext<string>, parent: MyApp) {
if (rf1 & RenderFlags.Create) {
elementStart(0, 'li');
{ text(1); }
elementEnd();
}
if (rf1 & RenderFlags.Update) {
textBinding(1, interpolation1('', row.$implicit, ''));
}
}
}, },
directives: () => [NgForOf] directives: () => [NgForOf]
}); });
@ -205,7 +209,8 @@ describe('@angular/common integration', () => {
/** /**
* <ul> * <ul>
* <li *ngFor="let row of items"> * <li *ngFor="let row of items">
* <span *ngFor="let cell of row.data">{{cell}} - {{ row.value }}</span> * <span *ngFor="let cell of row.data">{{cell}} - {{ row.value }} - {{ items.length }}
* </span>
* </li> * </li>
* </ul> * </ul>
*/ */
@ -216,14 +221,14 @@ describe('@angular/common integration', () => {
type: MyApp, type: MyApp,
factory: () => new MyApp(), factory: () => new MyApp(),
selectors: [['my-app']], selectors: [['my-app']],
template: (rf: RenderFlags, myApp: MyApp) => { template: (rf: RenderFlags, ctx: MyApp) => {
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
elementStart(0, 'ul'); elementStart(0, 'ul');
{ container(1, liTemplate, null, ['ngForOf', '']); } { container(1, liTemplate, null, ['ngForOf', '']); }
elementEnd(); elementEnd();
} }
if (rf & RenderFlags.Update) { 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) { function liTemplate(rf: RenderFlags, ctx: any) {
if (rf1 & RenderFlags.Create) { if (rf & RenderFlags.Create) {
elementStart(0, 'li'); elementStart(0, 'li');
{ container(1, spanTemplate, null, ['ngForOf', '']); } { container(1, spanTemplate, null, ['ngForOf', '']); }
elementEnd(); elementEnd();
} }
if (rf1 & RenderFlags.Update) { if (rf & RenderFlags.Update) {
const r1 = row.$implicit as any; const row = ctx.$implicit as any;
elementProperty(1, 'ngForOf', bind(r1.data)); elementProperty(1, 'ngForOf', bind(row.data));
} }
} }
function spanTemplate(rf1: RenderFlags, cell: any, row: any, myApp: MyApp) { function spanTemplate(rf: RenderFlags, ctx: any) {
if (rf1 & RenderFlags.Create) { if (rf & RenderFlags.Create) {
elementStart(0, 'span'); elementStart(0, 'span');
{ text(1); } { text(1); }
elementEnd(); elementEnd();
} }
if (rf1 & RenderFlags.Update) { if (rf & RenderFlags.Update) {
textBinding( const cell = ctx.$implicit;
1, interpolation2('', cell.$implicit, ' - ', (row.$implicit as any).value, '')); 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(); fixture.update();
expect(fixture.html) expect(fixture.html)
.toEqual( .toEqual(
'<ul><li><span>1 - first</span><span>2 - first</span></li><li><span>3 - second</span><span>4 - second</span></li></ul>'); '<ul><li><span>1 - first - 2</span><span>2 - first - 2</span></li><li><span>3 - second - 2</span><span>4 - second - 2</span></li></ul>');
// Remove the last item // Remove the last item
fixture.component.items.length = 1; fixture.component.items.length = 1;
fixture.update(); fixture.update();
expect(fixture.html) expect(fixture.html)
.toEqual('<ul><li><span>1 - first</span><span>2 - first</span></li></ul>'); .toEqual('<ul><li><span>1 - first - 1</span><span>2 - first - 1</span></li></ul>');
// Change an item // Change an item
fixture.component.items[0].data[0] = 'one'; fixture.component.items[0].data[0] = 'one';
fixture.update(); fixture.update();
expect(fixture.html) expect(fixture.html)
.toEqual('<ul><li><span>one - first</span><span>2 - first</span></li></ul>'); .toEqual('<ul><li><span>one - first - 1</span><span>2 - first - 1</span></li></ul>');
// Add an item // Add an item
fixture.component.items[1] = {data: ['three', '4'], value: 'third'}; fixture.component.items[1] = {data: ['three', '4'], value: 'third'};
fixture.update(); fixture.update();
expect(fixture.html) expect(fixture.html)
.toEqual( .toEqual(
'<ul><li><span>one - first</span><span>2 - first</span></li><li><span>three - third</span><span>4 - third</span></li></ul>'); '<ul><li><span>one - first - 2</span><span>2 - first - 2</span></li><li><span>three - third - 2</span><span>4 - third - 2</span></li></ul>');
});
it('should support multiple levels of embedded templates with listeners', () => {
/**
* <div *ngFor="let row of items">
* <p *ngFor="let cell of row.data">
* <span (click)="onClick(row.value, name)"></span>
* {{ row.value }} - {{ name }}
* </p>
* </div>
*/
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('<div><p><span></span>first - app</p></div>');
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('<div><p><span></span>first - new name</p></div>');
span.click();
expect(fixture.component.events).toEqual(['first', 'app', 'first', 'new name']);
});
it('should support skipping contexts', () => {
/**
* <div *ngFor="let row of items">
* <div *ngFor="let cell of row">
* <span *ngFor="let span of cell.data">
* {{ cell.value }} - {{ name }}
* </span>
* </div>
* </div>
*/
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(
`<div><div><span>one - app</span><span>one - app</span></div></div><div><div><span>two - app</span><span>two - app</span></div></div>`);
fixture.component.name = 'other';
fixture.update();
expect(fixture.html)
.toEqual(
`<div><div><span>one - other</span><span>one - other</span></div></div><div><div><span>two - other</span><span>two - other</span></div></div>`);
}); });
it('should support context for 9+ levels of embedded templates', () => { it('should support context for 9+ levels of embedded templates', () => {
@ -385,12 +564,12 @@ describe('@angular/common integration', () => {
type: MyApp, type: MyApp,
factory: () => new MyApp(), factory: () => new MyApp(),
selectors: [['my-app']], selectors: [['my-app']],
template: (rf: RenderFlags, myApp: MyApp) => { template: (rf: RenderFlags, ctx: MyApp) => {
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
container(0, itemTemplate0, null, ['ngForOf', '']); container(0, itemTemplate0, null, ['ngForOf', '']);
} }
if (rf & RenderFlags.Update) { 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) { function itemTemplate0(rf: RenderFlags, ctx: any) {
if (rf1 & RenderFlags.Create) { if (rf & RenderFlags.Create) {
elementStart(0, 'span'); elementStart(0, 'span');
{ container(1, itemTemplate1, null, ['ngForOf', '']); } { container(1, itemTemplate1, null, ['ngForOf', '']); }
elementEnd(); elementEnd();
} }
if (rf1 & RenderFlags.Update) { if (rf & RenderFlags.Update) {
const item = item0.$implicit as any; const item0 = ctx.$implicit as any;
elementProperty(1, 'ngForOf', bind(item.data)); elementProperty(1, 'ngForOf', bind(item0.data));
} }
} }
function itemTemplate1(rf1: RenderFlags, item1: any, item0: any, myApp: MyApp) { function itemTemplate1(rf: RenderFlags, ctx: any) {
if (rf1 & RenderFlags.Create) { if (rf & RenderFlags.Create) {
elementStart(0, 'span'); elementStart(0, 'span');
{ container(1, itemTemplate2, null, ['ngForOf', '']); } { container(1, itemTemplate2, null, ['ngForOf', '']); }
elementEnd(); elementEnd();
} }
if (rf1 & RenderFlags.Update) { if (rf & RenderFlags.Update) {
const item = item1.$implicit as any; const item1 = ctx.$implicit as any;
elementProperty(1, 'ngForOf', bind(item.data)); elementProperty(1, 'ngForOf', bind(item1.data));
} }
} }
function itemTemplate2(rf1: RenderFlags, item2: any, item1: any, item0: any, myApp: MyApp) { function itemTemplate2(rf: RenderFlags, ctx: any) {
if (rf1 & RenderFlags.Create) { if (rf & RenderFlags.Create) {
elementStart(0, 'span'); elementStart(0, 'span');
{ container(1, itemTemplate3, null, ['ngForOf', '']); } { container(1, itemTemplate3, null, ['ngForOf', '']); }
elementEnd(); elementEnd();
} }
if (rf1 & RenderFlags.Update) { if (rf & RenderFlags.Update) {
const item = item2.$implicit as any; const item2 = ctx.$implicit as any;
elementProperty(1, 'ngForOf', bind(item.data)); elementProperty(1, 'ngForOf', bind(item2.data));
} }
} }
function itemTemplate3( function itemTemplate3(rf: RenderFlags, ctx: any) {
rf1: RenderFlags, item3: any, item2: any, item1: any, item0: any, myApp: MyApp) { if (rf & RenderFlags.Create) {
if (rf1 & RenderFlags.Create) {
elementStart(0, 'span'); elementStart(0, 'span');
{ container(1, itemTemplate4, null, ['ngForOf', '']); } { container(1, itemTemplate4, null, ['ngForOf', '']); }
elementEnd(); elementEnd();
} }
if (rf1 & RenderFlags.Update) { if (rf & RenderFlags.Update) {
const item = item3.$implicit as any; const item3 = ctx.$implicit as any;
elementProperty(1, 'ngForOf', bind(item.data)); elementProperty(1, 'ngForOf', bind(item3.data));
} }
} }
function itemTemplate4( function itemTemplate4(rf: RenderFlags, ctx: any) {
rf1: RenderFlags, item4: any, item3: any, item2: any, item1: any, item0: any, if (rf & RenderFlags.Create) {
myApp: MyApp) {
if (rf1 & RenderFlags.Create) {
elementStart(0, 'span'); elementStart(0, 'span');
{ container(1, itemTemplate5, null, ['ngForOf', '']); } { container(1, itemTemplate5, null, ['ngForOf', '']); }
elementEnd(); elementEnd();
} }
if (rf1 & RenderFlags.Update) { if (rf & RenderFlags.Update) {
const item = item4.$implicit as any; const item4 = ctx.$implicit as any;
elementProperty(1, 'ngForOf', bind(item.data)); elementProperty(1, 'ngForOf', bind(item4.data));
} }
} }
function itemTemplate5( function itemTemplate5(rf: RenderFlags, ctx: any) {
rf1: RenderFlags, item5: any, item4: any, item3: any, item2: any, item1: any, item0: any, if (rf & RenderFlags.Create) {
myApp: MyApp) {
if (rf1 & RenderFlags.Create) {
elementStart(0, 'span'); elementStart(0, 'span');
{ container(1, itemTemplate6, null, ['ngForOf', '']); } { container(1, itemTemplate6, null, ['ngForOf', '']); }
elementEnd(); elementEnd();
} }
if (rf1 & RenderFlags.Update) { if (rf & RenderFlags.Update) {
const item = item5.$implicit as any; const item5 = ctx.$implicit as any;
elementProperty(1, 'ngForOf', bind(item.data)); elementProperty(1, 'ngForOf', bind(item5.data));
} }
} }
function itemTemplate6( function itemTemplate6(rf: RenderFlags, ctx: any) {
rf1: RenderFlags, item6: any, item5: any, item4: any, item3: any, item2: any, item1: any, if (rf & RenderFlags.Create) {
item0: any, myApp: MyApp) {
if (rf1 & RenderFlags.Create) {
elementStart(0, 'span'); elementStart(0, 'span');
{ container(1, itemTemplate7, null, ['ngForOf', '']); } { container(1, itemTemplate7, null, ['ngForOf', '']); }
elementEnd(); elementEnd();
} }
if (rf1 & RenderFlags.Update) { if (rf & RenderFlags.Update) {
const item = item6.$implicit as any; const item6 = ctx.$implicit as any;
elementProperty(1, 'ngForOf', bind(item.data)); elementProperty(1, 'ngForOf', bind(item6.data));
} }
} }
function itemTemplate7( function itemTemplate7(rf: RenderFlags, ctx: any) {
rf1: RenderFlags, item7: any, item6: any, item5: any, item4: any, item3: any, item2: any, if (rf & RenderFlags.Create) {
item1: any, item0: any, myApp: MyApp) {
if (rf1 & RenderFlags.Create) {
elementStart(0, 'span'); elementStart(0, 'span');
{ container(1, itemTemplate8, null, ['ngForOf', '']); } { container(1, itemTemplate8, null, ['ngForOf', '']); }
elementEnd(); elementEnd();
} }
if (rf1 & RenderFlags.Update) { if (rf & RenderFlags.Update) {
const item = item7.$implicit as any; const item7 = ctx.$implicit as any;
elementProperty(1, 'ngForOf', bind(item.data)); elementProperty(1, 'ngForOf', bind(item7.data));
} }
} }
function itemTemplate8( function itemTemplate8(rf: RenderFlags, ctx: any) {
rf1: RenderFlags, item8: any, item7: any, item6: any, item5: any, item4: any, item3: any, if (rf & RenderFlags.Create) {
item2: any, item1: any, item0: any, myApp: MyApp) {
if (rf1 & RenderFlags.Create) {
elementStart(0, 'span'); elementStart(0, 'span');
{ text(1); } { text(1); }
elementEnd(); elementEnd();
} }
if (rf1 & RenderFlags.Update) { if (rf & RenderFlags.Update) {
textBinding( const value = ctx.$implicit;
1, interpolationV([ const item7 = nextContext().$implicit;
'', item8.$implicit, '.', item7.$implicit.value, '.', item6.$implicit.value, const item6 = nextContext().$implicit;
'.', item5.$implicit.value, '.', item4.$implicit.value, '.', item3.$implicit.value, const item5 = nextContext().$implicit;
'.', item2.$implicit.value, '.', item1.$implicit.value, '.', item0.$implicit.value, const item4 = nextContext().$implicit;
'.', myApp.value, '' 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', () => {
* <div *ngIf="showing">{{ valueOne }}</div> * <div *ngIf="showing">{{ valueOne }}</div>
* <div *ngIf="showing">{{ valueTwo }}</div> * <div *ngIf="showing">{{ valueTwo }}</div>
*/ */
template: (rf: RenderFlags, myApp: MyApp) => { template: (rf: RenderFlags, ctx: MyApp) => {
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
container(0, templateOne, undefined, ['ngIf', '']); container(0, templateOne, undefined, ['ngIf', '']);
container(1, templateTwo, undefined, ['ngIf', '']); container(1, templateTwo, undefined, ['ngIf', '']);
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
elementProperty(0, 'ngIf', bind(myApp.showing)); elementProperty(0, 'ngIf', bind(ctx.showing));
elementProperty(1, 'ngIf', bind(myApp.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) { if (rf & RenderFlags.Create) {
elementStart(0, 'div'); elementStart(0, 'div');
{ text(1); } { text(1); }
elementEnd(); elementEnd();
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
const myApp = nextContext();
textBinding(1, bind(myApp.valueOne)); textBinding(1, bind(myApp.valueOne));
} }
} }
function templateTwo(rf: RenderFlags, ctx: any, myApp: MyApp) { function templateTwo(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
elementStart(0, 'div'); elementStart(0, 'div');
{ text(1); } { text(1); }
elementEnd(); elementEnd();
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
const myApp = nextContext();
textBinding(1, bind(myApp.valueTwo)); textBinding(1, bind(myApp.valueTwo));
} }
} }
@ -600,6 +778,83 @@ describe('@angular/common integration', () => {
expect(fixture.html).toEqual('<div>$$one$$</div><div>$$two$$</div>'); expect(fixture.html).toEqual('<div>$$one$$</div><div>$$two$$</div>');
}); });
it('should handle nested ngIfs with no intermediate context vars', () => {
/**
* <div *ngIf="showing">
* <div *ngIf="outerShowing">
* <div *ngIf="innerShowing'>
* {{ name }}
* </div>
* </div>
* </div>
*/
class AppComponent {
showing = true;
outerShowing = true;
innerShowing = true;
name = 'App name';
static ngComponentDef = defineComponent({
type: AppComponent,
factory: () => new AppComponent(),
selectors: [['my-app']],
template: (rf: RenderFlags, ctx: AppComponent) => {
if (rf & RenderFlags.Create) {
container(0, divTemplate, undefined, ['ngIf', '']);
}
if (rf & RenderFlags.Update) {
elementProperty(0, 'ngIf', bind(ctx.showing));
}
},
directives: () => [NgIf]
});
}
function divTemplate(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
elementStart(0, 'div');
{ container(1, outerDivTemplate, undefined, ['ngIf', '']); }
elementEnd();
}
if (rf & RenderFlags.Update) {
const app = nextContext();
elementProperty(1, 'ngIf', bind(app.outerShowing));
}
}
function outerDivTemplate(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
elementStart(0, 'div');
{ container(1, innerDivTemplate, undefined, ['ngIf', '']); }
elementEnd();
}
if (rf & RenderFlags.Update) {
const app = nextContext(2);
elementProperty(1, 'ngIf', bind(app.innerShowing));
}
}
function innerDivTemplate(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
elementStart(0, 'div');
{ text(1); }
elementEnd();
}
if (rf & RenderFlags.Update) {
const app = nextContext(3);
textBinding(1, bind(app.name));
}
}
const fixture = new ComponentFixture(AppComponent);
expect(fixture.html).toEqual(`<div><div><div>App name</div></div></div>`);
fixture.component.name = 'Other name';
fixture.update();
expect(fixture.html).toEqual(`<div><div><div>Other name</div></div></div>`);
});
}); });
describe('NgTemplateOutlet', () => { describe('NgTemplateOutlet', () => {

View File

@ -10,7 +10,7 @@
import {DoCheck, Input, TemplateRef, ViewContainerRef, ViewEncapsulation, createInjector, defineInjectable, defineInjector} from '../../src/core'; import {DoCheck, Input, TemplateRef, ViewContainerRef, ViewEncapsulation, createInjector, defineInjectable, defineInjector} from '../../src/core';
import {getRenderedText} from '../../src/render3/component'; import {getRenderedText} from '../../src/render3/component';
import {AttributeMarker, ComponentFactory, LifecycleHooksFeature, defineComponent, directiveInject, markDirty} from '../../src/render3/index'; import {AttributeMarker, ComponentFactory, LifecycleHooksFeature, defineComponent, directiveInject, markDirty} from '../../src/render3/index';
import {bind, container, containerRefreshEnd, containerRefreshStart, element, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, text, textBinding, tick} from '../../src/render3/instructions'; import {bind, container, containerRefreshEnd, containerRefreshStart, element, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, nextContext, text, textBinding, tick} from '../../src/render3/instructions';
import {ComponentDefInternal, DirectiveDefInternal, RenderFlags} from '../../src/render3/interfaces/definition'; import {ComponentDefInternal, DirectiveDefInternal, RenderFlags} from '../../src/render3/interfaces/definition';
import {createRendererType2} from '../../src/view/index'; import {createRendererType2} from '../../src/view/index';
@ -394,22 +394,24 @@ describe('recursive components', () => {
}); });
} }
function IfTemplate(rf1: RenderFlags, left: any, parent: NgIfTree) { function IfTemplate(rf: RenderFlags, left: any) {
if (rf1 & RenderFlags.Create) { if (rf & RenderFlags.Create) {
elementStart(0, 'ng-if-tree'); elementStart(0, 'ng-if-tree');
elementEnd(); elementEnd();
} }
if (rf1 & RenderFlags.Update) { if (rf & RenderFlags.Update) {
const parent = nextContext();
elementProperty(0, 'data', bind(parent.data.left)); elementProperty(0, 'data', bind(parent.data.left));
} }
} }
function IfTemplate2(rf1: RenderFlags, right: any, parent: NgIfTree) { function IfTemplate2(rf: RenderFlags, right: any) {
if (rf1 & RenderFlags.Create) { if (rf & RenderFlags.Create) {
elementStart(0, 'ng-if-tree'); elementStart(0, 'ng-if-tree');
elementEnd(); elementEnd();
} }
if (rf1 & RenderFlags.Update) { if (rf & RenderFlags.Update) {
const parent = nextContext();
elementProperty(0, 'data', bind(parent.data.right)); elementProperty(0, 'data', bind(parent.data.right));
} }
} }

View File

@ -825,7 +825,7 @@ describe('content projection', () => {
}, [NgIf]); }, [NgIf]);
function IfTemplate(rf1: RenderFlags, ctx: any, child: any) { function IfTemplate(rf1: RenderFlags, ctx: any) {
if (rf1 & RenderFlags.Create) { if (rf1 & RenderFlags.Create) {
projectionDef(); projectionDef();
projection(0); projection(0);
@ -890,7 +890,7 @@ describe('content projection', () => {
}, [NgIf]); }, [NgIf]);
function IfTemplate(rf: RenderFlags, ctx: any, child: any) { function IfTemplate(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
projectionDef(); projectionDef();
projection(0); projection(0);

View File

@ -12,7 +12,7 @@ import {RenderFlags} from '@angular/core/src/render3/interfaces/definition';
import {defineComponent} from '../../src/render3/definition'; import {defineComponent} from '../../src/render3/definition';
import {bloomAdd, bloomFindPossibleInjector, getOrCreateNodeInjector, injectAttribute} from '../../src/render3/di'; import {bloomAdd, bloomFindPossibleInjector, getOrCreateNodeInjector, injectAttribute} from '../../src/render3/di';
import {NgOnChangesFeature, PublicFeature, defineDirective, directiveInject, injectChangeDetectorRef, injectElementRef, injectTemplateRef, injectViewContainerRef} from '../../src/render3/index'; import {NgOnChangesFeature, PublicFeature, defineDirective, directiveInject, injectChangeDetectorRef, injectElementRef, injectTemplateRef, injectViewContainerRef} from '../../src/render3/index';
import {bind, container, containerRefreshEnd, containerRefreshStart, createLNode, createLViewData, createTView, element, elementEnd, elementStart, embeddedViewEnd, embeddedViewStart, enterView, interpolation2, leaveView, load, projection, projectionDef, text, textBinding} from '../../src/render3/instructions'; import {bind, container, containerRefreshEnd, containerRefreshStart, createLNode, createLViewData, createTView, element, elementEnd, elementStart, embeddedViewEnd, embeddedViewStart, enterView, interpolation2, leaveView, projection, projectionDef, reference, text, textBinding} from '../../src/render3/instructions';
import {LInjector} from '../../src/render3/interfaces/injector'; import {LInjector} from '../../src/render3/interfaces/injector';
import {AttributeMarker, TNodeType} from '../../src/render3/interfaces/node'; import {AttributeMarker, TNodeType} from '../../src/render3/interfaces/node';
import {LViewFlags} from '../../src/render3/interfaces/view'; import {LViewFlags} from '../../src/render3/interfaces/view';
@ -40,9 +40,8 @@ describe('di', () => {
{ text(2); } { text(2); }
elementEnd(); elementEnd();
} }
let tmp: any;
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
tmp = load(1); const tmp = reference(1) as any;
textBinding(2, bind(tmp.value)); textBinding(2, bind(tmp.value));
} }
} }
@ -105,9 +104,8 @@ describe('di', () => {
} }
elementEnd(); elementEnd();
} }
let tmp: any;
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
tmp = load(2); const tmp = reference(2) as any;
textBinding(3, bind(tmp.value)); textBinding(3, bind(tmp.value));
} }
} }
@ -815,11 +813,9 @@ describe('di', () => {
elementEnd(); elementEnd();
} }
let tmp1: any;
let tmp2: any;
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
tmp1 = load(1); const tmp1 = reference(1) as any;
tmp2 = load(2); const tmp2 = reference(2) as any;
textBinding(3, interpolation2('', tmp2.value, '-', tmp1.value, '')); textBinding(3, interpolation2('', tmp2.value, '-', tmp1.value, ''));
} }
} }
@ -870,11 +866,9 @@ describe('di', () => {
}, undefined, ['dir', '', 'dirSame', ''], ['dir', 'dir', 'dirSame', 'dirSame']); }, undefined, ['dir', '', 'dirSame', ''], ['dir', 'dir', 'dirSame', 'dirSame']);
text(3); text(3);
} }
let tmp1: any;
let tmp2: any;
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
tmp1 = load(1); const tmp1 = reference(1) as any;
tmp2 = load(2); const tmp2 = reference(2) as any;
textBinding(3, interpolation2('', tmp1.value, '-', tmp2.value, '')); textBinding(3, interpolation2('', tmp1.value, '-', tmp2.value, ''));
} }
} }
@ -925,11 +919,9 @@ describe('di', () => {
{ text(3); } { text(3); }
elementEnd(); elementEnd();
} }
let tmp1: any;
let tmp2: any;
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
tmp1 = load(1); const tmp1 = reference(1) as any;
tmp2 = load(2); const tmp2 = reference(2) as any;
textBinding(3, interpolation2('', tmp1.value, '-', tmp2.value, '')); textBinding(3, interpolation2('', tmp1.value, '-', tmp2.value, ''));
} }
} }
@ -1016,9 +1008,8 @@ describe('di', () => {
element(0, 'my-comp', ['dir', '', 'dirSame', ''], ['dir', 'dir']); element(0, 'my-comp', ['dir', '', 'dirSame', ''], ['dir', 'dir']);
text(2); text(2);
} }
let tmp: any;
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
tmp = load(1); const tmp = reference(1) as any;
textBinding(2, bind(tmp.value)); textBinding(2, bind(tmp.value));
} }
}, directives); }, directives);
@ -1048,9 +1039,8 @@ describe('di', () => {
{ text(2); } { text(2); }
elementEnd(); elementEnd();
} }
let tmp: any;
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
tmp = load(1); const tmp = reference(1) as any;
textBinding(2, bind(tmp.value)); textBinding(2, bind(tmp.value));
} }
}, },
@ -1087,8 +1077,8 @@ describe('di', () => {
elementEnd(); elementEnd();
text(3); text(3);
} }
const tmp = load(2) as any;
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
const tmp = reference(2) as any;
textBinding(3, bind(tmp.value)); textBinding(3, bind(tmp.value));
} }
}, },
@ -1134,9 +1124,8 @@ describe('di', () => {
{ text(2); } { text(2); }
elementEnd(); elementEnd();
} }
let tmp: any;
if (rf1 & RenderFlags.Update) { if (rf1 & RenderFlags.Update) {
tmp = load(1); const tmp = reference(1) as any;
textBinding(2, bind(tmp.value)); textBinding(2, bind(tmp.value));
} }
} }
@ -1183,9 +1172,8 @@ describe('di', () => {
{ text(2); } { text(2); }
elementEnd(); elementEnd();
} }
let tmp: any;
if (rf1 & RenderFlags.Update) { if (rf1 & RenderFlags.Update) {
tmp = load(1); const tmp = reference(1) as any;
textBinding(2, bind(tmp.value)); textBinding(2, bind(tmp.value));
} }
} }
@ -1382,11 +1370,9 @@ describe('di', () => {
{ text(3); } { text(3); }
elementEnd(); elementEnd();
} }
let tmp1: any;
let tmp2: any;
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
tmp1 = load(1); const tmp1 = reference(1) as any;
tmp2 = load(2); const tmp2 = reference(2) as any;
textBinding(3, interpolation2('', tmp1.value, '-', tmp2.value, '')); textBinding(3, interpolation2('', tmp1.value, '-', tmp2.value, ''));
} }
embeddedViewEnd(); embeddedViewEnd();

View File

@ -7,7 +7,7 @@
*/ */
import {AttributeMarker, defineComponent, defineDirective} from '../../src/render3/index'; 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 {bind, container, containerRefreshEnd, containerRefreshStart, element, elementAttribute, elementClassProp, elementEnd, elementProperty, elementStart, elementStyling, elementStylingApply, embeddedViewEnd, embeddedViewStart, interpolation2, nextContext, reference, text, textBinding} from '../../src/render3/instructions';
import {InitialStylingFlags, RenderFlags} from '../../src/render3/interfaces/definition'; import {InitialStylingFlags, RenderFlags} from '../../src/render3/interfaces/definition';
import {NgIf} from './common_with_def'; import {NgIf} from './common_with_def';
@ -23,7 +23,7 @@ describe('exports', () => {
text(2); text(2);
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
const tmp = load(1) as any; const tmp = reference(1) as any;
textBinding(2, tmp.value); textBinding(2, tmp.value);
} }
} }
@ -40,7 +40,7 @@ describe('exports', () => {
text(2); text(2);
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
const tmp = load(1) as any; const tmp = reference(1) as any;
textBinding(2, tmp.name); textBinding(2, tmp.name);
} }
} }
@ -94,7 +94,7 @@ describe('exports', () => {
element(2, 'div', ['myDir', '']); element(2, 'div', ['myDir', '']);
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
const tmp = load(1) as any; const tmp = reference(1) as any;
elementProperty(2, 'myDir', bind(tmp)); elementProperty(2, 'myDir', bind(tmp));
} }
} }
@ -112,7 +112,7 @@ describe('exports', () => {
text(2); text(2);
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
const tmp = load(1) as any; const tmp = reference(1) as any;
textBinding(2, tmp.name); textBinding(2, tmp.name);
} }
} }
@ -153,7 +153,7 @@ describe('exports', () => {
element(1, 'input', ['value', 'one'], ['myInput', '']); element(1, 'input', ['value', 'one'], ['myInput', '']);
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
const tmp = load(2) as any; const tmp = reference(2) as any;
textBinding(0, bind(tmp.value)); textBinding(0, bind(tmp.value));
} }
} }
@ -170,7 +170,7 @@ describe('exports', () => {
element(1, 'input', ['value', 'one'], ['myInput', '']); element(1, 'input', ['value', 'one'], ['myInput', '']);
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
const tmp = load(2) as any; const tmp = reference(2) as any;
elementProperty(0, 'title', bind(tmp.value)); elementProperty(0, 'title', bind(tmp.value));
} }
} }
@ -186,7 +186,7 @@ describe('exports', () => {
element(1, 'input', ['value', 'one'], ['myInput', '']); element(1, 'input', ['value', 'one'], ['myInput', '']);
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
const tmp = load(2) as any; const tmp = reference(2) as any;
elementAttribute(0, 'aria-label', bind(tmp.value)); elementAttribute(0, 'aria-label', bind(tmp.value));
} }
} }
@ -204,7 +204,7 @@ describe('exports', () => {
element(1, 'input', ['type', 'checkbox', 'checked', 'true'], ['myInput', '']); element(1, 'input', ['type', 'checkbox', 'checked', 'true'], ['myInput', '']);
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
const tmp = load(2) as any; const tmp = reference(2) as any;
elementClassProp(0, 0, tmp.checked); elementClassProp(0, 0, tmp.checked);
elementStylingApply(0); elementStylingApply(0);
} }
@ -251,7 +251,7 @@ describe('exports', () => {
element(1, 'comp', null, ['myComp', '']); element(1, 'comp', null, ['myComp', '']);
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
const tmp = load(2) as any; const tmp = reference(2) as any;
elementProperty(0, 'myDir', bind(tmp)); elementProperty(0, 'myDir', bind(tmp));
} }
} }
@ -271,8 +271,8 @@ describe('exports', () => {
element(4, 'input', ['value', 'one'], ['myInput', '']); element(4, 'input', ['value', 'one'], ['myInput', '']);
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
const tmp1 = load(3) as any; const tmp1 = reference(3) as any;
const tmp2 = load(5) as any; const tmp2 = reference(5) as any;
textBinding(0, bind(tmp2.value)); textBinding(0, bind(tmp2.value));
textBinding(1, bind(tmp1.name)); textBinding(1, bind(tmp1.name));
} }
@ -314,7 +314,7 @@ describe('exports', () => {
element(1, 'input', ['value', 'one'], ['myInput', '']); element(1, 'input', ['value', 'one'], ['myInput', '']);
} }
if (rf1 & RenderFlags.Update) { if (rf1 & RenderFlags.Update) {
const tmp = load(2) as any; const tmp = reference(2) as any;
textBinding(0, bind(tmp.value)); textBinding(0, bind(tmp.value));
} }
} }
@ -355,7 +355,7 @@ describe('exports', () => {
} }
}, [NgIf]); }, [NgIf]);
function outerTemplate(rf: RenderFlags, outer: any, app: any) { function outerTemplate(rf: RenderFlags, outer: any) {
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
elementStart(0, 'div'); elementStart(0, 'div');
{ {
@ -367,23 +367,26 @@ describe('exports', () => {
elementEnd(); elementEnd();
} }
const outerInput = reference(1, 1) as any;
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
const app = nextContext();
const outerInput = reference(1) as any;
textBinding(1, bind(outerInput.value)); textBinding(1, bind(outerInput.value));
elementProperty(4, 'ngIf', bind(app.inner)); elementProperty(4, 'ngIf', bind(app.inner));
} }
} }
function innerTemplate(rf: RenderFlags, inner: any, outer: any, app: any) { function innerTemplate(rf: RenderFlags, inner: any) {
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
elementStart(0, 'div'); elementStart(0, 'div');
{ text(1); } { text(1); }
elementEnd(); elementEnd();
} }
const outerInput = reference(2, 1) as any;
const innerInput = reference(1, 3) as any;
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
nextContext();
const innerInput = reference(3) as any;
nextContext();
const outerInput = reference(1) as any;
textBinding(1, interpolation2('', outerInput.value, ' - ', innerInput.value, '')); textBinding(1, interpolation2('', outerInput.value, ' - ', innerInput.value, ''));
} }
} }

View File

@ -9,7 +9,7 @@
import {EventEmitter} from '@angular/core'; import {EventEmitter} from '@angular/core';
import {defineComponent, defineDirective, tick} from '../../src/render3/index'; import {defineComponent, defineDirective, tick} from '../../src/render3/index';
import {NO_CHANGE, bind, container, containerRefreshEnd, containerRefreshStart, element, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, interpolation1, listener, load, loadDirective, text, textBinding} from '../../src/render3/instructions'; import {NO_CHANGE, bind, container, containerRefreshEnd, containerRefreshStart, element, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, interpolation1, listener, loadDirective, reference, text, textBinding} from '../../src/render3/instructions';
import {RenderFlags} from '../../src/render3/interfaces/definition'; import {RenderFlags} from '../../src/render3/interfaces/definition';
import {ComponentFixture, renderToHtml} from './render_util'; import {ComponentFixture, renderToHtml} from './render_util';
@ -554,7 +554,7 @@ describe('elementProperty', () => {
text(2); text(2);
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
const tmp = load(1) as any; const tmp = reference(1) as any;
textBinding(2, bind(tmp.role)); textBinding(2, bind(tmp.role));
} }
}, },

View File

@ -8,8 +8,8 @@
import {Component, ComponentFactoryResolver, Directive, EmbeddedViewRef, NgModuleRef, Pipe, PipeTransform, RendererFactory2, TemplateRef, ViewContainerRef, createInjector, defineInjector, ɵAPP_ROOT as APP_ROOT, ɵNgModuleDef as NgModuleDef} from '../../src/core'; import {Component, ComponentFactoryResolver, Directive, EmbeddedViewRef, NgModuleRef, Pipe, PipeTransform, RendererFactory2, TemplateRef, ViewContainerRef, createInjector, defineInjector, ɵAPP_ROOT as APP_ROOT, ɵNgModuleDef as NgModuleDef} from '../../src/core';
import {getOrCreateNodeInjectorForNode, getOrCreateTemplateRef} from '../../src/render3/di'; import {getOrCreateNodeInjectorForNode, getOrCreateTemplateRef} from '../../src/render3/di';
import {AttributeMarker,NgOnChangesFeature, defineComponent, defineDirective, definePipe, injectComponentFactoryResolver, injectTemplateRef, injectViewContainerRef} from '../../src/render3/index'; import {AttributeMarker, NgOnChangesFeature, defineComponent, defineDirective, definePipe, injectComponentFactoryResolver, injectTemplateRef, injectViewContainerRef} from '../../src/render3/index';
import {bind, container, containerRefreshEnd, containerRefreshStart, element, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, interpolation1, interpolation3, load, loadDirective, projection, projectionDef, reserveSlots, text, textBinding} from '../../src/render3/instructions'; import {bind, container, containerRefreshEnd, containerRefreshStart, element, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, interpolation1, interpolation3, load, loadDirective, nextContext, projection, projectionDef, reserveSlots, text, textBinding} from '../../src/render3/instructions';
import {RenderFlags} from '../../src/render3/interfaces/definition'; import {RenderFlags} from '../../src/render3/interfaces/definition';
import {NgModuleFactory} from '../../src/render3/ng_module_ref'; import {NgModuleFactory} from '../../src/render3/ng_module_ref';
import {pipe, pipeBind1} from '../../src/render3/pipe'; import {pipe, pipeBind1} from '../../src/render3/pipe';
@ -521,7 +521,7 @@ describe('ViewContainerRef', () => {
}, [Child]); }, [Child]);
function fooTemplate(rf1: RenderFlags, ctx: any, parent: any) { function fooTemplate(rf1: RenderFlags, ctx: any) {
if (rf1 & RenderFlags.Create) { if (rf1 & RenderFlags.Create) {
elementStart(0, 'div'); elementStart(0, 'div');
{ text(1); } { text(1); }
@ -529,6 +529,7 @@ describe('ViewContainerRef', () => {
} }
if (rf1 & RenderFlags.Update) { if (rf1 & RenderFlags.Update) {
const parent = nextContext();
textBinding(1, bind(parent.name)); textBinding(1, bind(parent.name));
} }
} }
@ -612,32 +613,34 @@ describe('ViewContainerRef', () => {
}, [LoopComp]); }, [LoopComp]);
function rowTemplate(rf1: RenderFlags, row: any, parent: any) { function rowTemplate(rf: RenderFlags, ctx: any) {
if (rf1 & RenderFlags.Create) { if (rf & RenderFlags.Create) {
container(0, cellTemplate); container(0, cellTemplate);
elementStart(1, 'loop-comp'); elementStart(1, 'loop-comp');
elementEnd(); elementEnd();
} }
if (rf1 & RenderFlags.Update) { if (rf & RenderFlags.Update) {
const row = ctx.$implicit as any;
// Hack until we have local refs for templates // Hack until we have local refs for templates
const cellTemplateRef = getOrCreateTemplateRef(getOrCreateNodeInjectorForNode(load(0))); const cellTemplateRef = getOrCreateTemplateRef(getOrCreateNodeInjectorForNode(load(0)));
elementProperty(1, 'tpl', bind(cellTemplateRef)); elementProperty(1, 'tpl', bind(cellTemplateRef));
elementProperty(1, 'rows', bind(row.$implicit.data)); elementProperty(1, 'rows', bind(row.data));
} }
} }
function cellTemplate(rf1: RenderFlags, cell: any, row: any, parent: any) { function cellTemplate(rf: RenderFlags, ctx: any) {
if (rf1 & RenderFlags.Create) { if (rf & RenderFlags.Create) {
elementStart(0, 'div'); elementStart(0, 'div');
{ text(1); } { text(1); }
elementEnd(); elementEnd();
} }
if (rf1 & RenderFlags.Update) { if (rf & RenderFlags.Update) {
textBinding( const cell = ctx.$implicit as any;
1, interpolation3( const row = nextContext().$implicit as any;
'', cell.$implicit, ' - ', row.$implicit.value, ' - ', parent.name, '')); const parent = nextContext();
textBinding(1, interpolation3('', cell, ' - ', row.value, ' - ', parent.name, ''));
} }
} }