fix(ivy): support property values changed in ngOnChanges (forward rref case) (#29054)

PR Close #29054
This commit is contained in:
Marc Laval 2019-03-01 14:39:28 +01:00 committed by Andrew Kushnir
parent 6215799055
commit 25166d4f41
24 changed files with 609 additions and 114 deletions

View File

@ -12,7 +12,7 @@
"master": { "master": {
"uncompressed": { "uncompressed": {
"runtime": 1440, "runtime": 1440,
"main": 13659, "main": 13921,
"polyfills": 38390 "polyfills": 38390
} }
} }

View File

@ -798,6 +798,7 @@ describe('compiler compliance', () => {
if (rf & 2) { if (rf & 2) {
const $myComp$ = $r3$.ɵnextContext(); const $myComp$ = $r3$.ɵnextContext();
const $foo$ = $r3$.ɵreference(1); const $foo$ = $r3$.ɵreference(1);
$r3$.ɵflushHooksUpTo(1);
$r3$.ɵtextBinding(1, $r3$.ɵinterpolation2("", $myComp$.salutation, " ", $foo$, "")); $r3$.ɵtextBinding(1, $r3$.ɵinterpolation2("", $myComp$.salutation, " ", $foo$, ""));
} }
} }
@ -1255,6 +1256,7 @@ describe('compiler compliance', () => {
} }
if (rf & 2) { if (rf & 2) {
$r3$.ɵelementProperty(0, "ngIf", $r3$.ɵbind(ctx.visible)); $r3$.ɵelementProperty(0, "ngIf", $r3$.ɵbind(ctx.visible));
$r3$.ɵflushHooksUpTo(1);
$r3$.ɵelementProperty(1, "ngIf", $r3$.ɵbind(ctx.visible)); $r3$.ɵelementProperty(1, "ngIf", $r3$.ɵbind(ctx.visible));
} }
} }
@ -1947,6 +1949,7 @@ describe('compiler compliance', () => {
} }
if (rf & 2) { if (rf & 2) {
$r3$.ɵtextBinding(0, $r3$.ɵinterpolation1("", $r3$.ɵpipeBind2(1, 3, $r3$.ɵpipeBind2(2, 6, ctx.name, ctx.size), ctx.size), "")); $r3$.ɵtextBinding(0, $r3$.ɵinterpolation1("", $r3$.ɵpipeBind2(1, 3, $r3$.ɵpipeBind2(2, 6, ctx.name, ctx.size), ctx.size), ""));
$r3$.ɵflushHooksUpTo(4);
$r3$.ɵtextBinding(4, $r3$.ɵinterpolation2("", $r3$.ɵpipeBindV(5, 9, $r3$.ɵpureFunction1(18, $c0$, ctx.name)), " ", (ctx.name ? 1 : $r3$.ɵpipeBind1(6, 16, 2)), "")); $r3$.ɵtextBinding(4, $r3$.ɵinterpolation2("", $r3$.ɵpipeBindV(5, 9, $r3$.ɵpureFunction1(18, $c0$, ctx.name)), " ", (ctx.name ? 1 : $r3$.ɵpipeBind1(6, 16, 2)), ""));
} }
}, },
@ -2061,6 +2064,7 @@ describe('compiler compliance', () => {
} }
if (rf & 2) { if (rf & 2) {
const $user$ = $r3$.ɵreference(1); const $user$ = $r3$.ɵreference(1);
$r3$.ɵflushHooksUpTo(2);
$r3$.ɵtextBinding(2, $r3$.ɵinterpolation1("Hello ", $user$.value, "!")); $r3$.ɵtextBinding(2, $r3$.ɵinterpolation1("Hello ", $user$.value, "!"));
} }
}, },
@ -2123,6 +2127,7 @@ describe('compiler compliance', () => {
$r3$.ɵnextContext(); $r3$.ɵnextContext();
const $foo$ = $r3$.ɵreference(1); const $foo$ = $r3$.ɵreference(1);
const $baz$ = $r3$.ɵreference(5); const $baz$ = $r3$.ɵreference(5);
$r3$.ɵflushHooksUpTo(1);
$r3$.ɵtextBinding(1, $r3$.ɵinterpolation3("", $foo$, "-", $bar$, "-", $baz$, "")); $r3$.ɵtextBinding(1, $r3$.ɵinterpolation3("", $foo$, "-", $bar$, "-", $baz$, ""));
} }
} }
@ -2138,6 +2143,7 @@ describe('compiler compliance', () => {
const $bar$ = $r3$.ɵreference(4); const $bar$ = $r3$.ɵreference(4);
$r3$.ɵnextContext(); $r3$.ɵnextContext();
const $foo$ = $r3$.ɵreference(1); const $foo$ = $r3$.ɵreference(1);
$r3$.ɵflushHooksUpTo(1);
$r3$.ɵtextBinding(1, $r3$.ɵinterpolation2(" ", $foo$, "-", $bar$, " ")); $r3$.ɵtextBinding(1, $r3$.ɵinterpolation2(" ", $foo$, "-", $bar$, " "));
} }
} }
@ -2157,6 +2163,7 @@ describe('compiler compliance', () => {
} }
if (rf & 2) { if (rf & 2) {
const $foo$ = $r3$.ɵreference(1); const $foo$ = $r3$.ɵreference(1);
$r3$.ɵflushHooksUpTo(2);
$r3$.ɵtextBinding(2, $r3$.ɵinterpolation1(" ", $foo$, " ")); $r3$.ɵtextBinding(2, $r3$.ɵinterpolation1(" ", $foo$, " "));
} }
}, },
@ -2209,6 +2216,7 @@ describe('compiler compliance', () => {
if (rf & 2) { if (rf & 2) {
const $item$ = $i0$.ɵnextContext().$implicit; const $item$ = $i0$.ɵnextContext().$implicit;
const $foo$ = $i0$.ɵreference(2); const $foo$ = $i0$.ɵreference(2);
$r3$.ɵflushHooksUpTo(1);
$i0$.ɵtextBinding(1, $i0$.ɵinterpolation2(" ", $foo$, " - ", $item$, " ")); $i0$.ɵtextBinding(1, $i0$.ɵinterpolation2(" ", $foo$, " - ", $item$, " "));
} }
} }
@ -2222,6 +2230,7 @@ describe('compiler compliance', () => {
} }
if (rf & 2) { if (rf & 2) {
const $app$ = $i0$.ɵnextContext(); const $app$ = $i0$.ɵnextContext();
$r3$.ɵflushHooksUpTo(3);
$i0$.ɵelementProperty(3, "ngIf", $i0$.ɵbind($app$.showing)); $i0$.ɵelementProperty(3, "ngIf", $i0$.ɵbind($app$.showing));
} }
} }
@ -2313,6 +2322,7 @@ describe('compiler compliance', () => {
} }
if (rf & 2) { if (rf & 2) {
$r3$.ɵelementProperty(0, "name", $r3$.ɵbind(ctx.name1)); $r3$.ɵelementProperty(0, "name", $r3$.ɵbind(ctx.name1));
$r3$.ɵflushHooksUpTo(1);
$r3$.ɵelementProperty(1, "name", $r3$.ɵbind(ctx.name2)); $r3$.ɵelementProperty(1, "name", $r3$.ɵbind(ctx.name2));
} }
}, },
@ -2443,7 +2453,10 @@ describe('compiler compliance', () => {
$r3$.ɵtemplate(1, MyComponent__svg_g_1_Template, 2, 0, "g", $t1_attrs$); $r3$.ɵtemplate(1, MyComponent__svg_g_1_Template, 2, 0, "g", $t1_attrs$);
$r3$.ɵelementEnd(); $r3$.ɵelementEnd();
} }
if (rf & 2) { $r3$.ɵelementProperty(1,"forOf",$r3$.ɵbind(ctx.items)); } if (rf & 2) {
$r3$.ɵflushHooksUpTo(1);
$r3$.ɵelementProperty(1,"forOf",$r3$.ɵbind(ctx.items));
}
}, },
directives: function() { return [ForOfDirective]; }, directives: function() { return [ForOfDirective]; },
encapsulation: 2 encapsulation: 2
@ -2505,6 +2518,7 @@ describe('compiler compliance', () => {
} }
if (rf & 2) { if (rf & 2) {
const $item$ = ctx.$implicit; const $item$ = ctx.$implicit;
$r3$.ɵflushHooksUpTo(1);
$r3$.ɵtextBinding(1, $r3$.ɵinterpolation1("", $item$.name, "")); $r3$.ɵtextBinding(1, $r3$.ɵinterpolation1("", $item$.name, ""));
} }
} }
@ -2522,6 +2536,7 @@ describe('compiler compliance', () => {
$r3$.ɵelementEnd(); $r3$.ɵelementEnd();
} }
if (rf & 2) { if (rf & 2) {
$r3$.ɵflushHooksUpTo(1);
$r3$.ɵelementProperty(1, "forOf", $r3$.ɵbind(ctx.items)); $r3$.ɵelementProperty(1, "forOf", $r3$.ɵbind(ctx.items));
} }
}, },
@ -2586,6 +2601,7 @@ describe('compiler compliance', () => {
if (rf & 2) { if (rf & 2) {
const $info$ = ctx.$implicit; const $info$ = ctx.$implicit;
const $item$ = $r3$.ɵnextContext().$implicit; const $item$ = $r3$.ɵnextContext().$implicit;
$r3$.ɵflushHooksUpTo(1);
$r3$.ɵtextBinding(1, $r3$.ɵinterpolation2(" ", $item$.name, ": ", $info$.description, " ")); $r3$.ɵtextBinding(1, $r3$.ɵinterpolation2(" ", $item$.name, ": ", $info$.description, " "));
} }
} }
@ -2603,7 +2619,9 @@ describe('compiler compliance', () => {
} }
if (rf & 2) { if (rf & 2) {
const $item$ = ctx.$implicit; const $item$ = ctx.$implicit;
$r3$.ɵflushHooksUpTo(2);
$r3$.ɵtextBinding(2, $r3$.ɵinterpolation1("", IDENT.name, "")); $r3$.ɵtextBinding(2, $r3$.ɵinterpolation1("", IDENT.name, ""));
$r3$.ɵflushHooksUpTo(4);
$r3$.ɵelementProperty(4, "forOf", $r3$.ɵbind(IDENT.infos)); $r3$.ɵelementProperty(4, "forOf", $r3$.ɵbind(IDENT.infos));
} }
} }
@ -2622,6 +2640,7 @@ describe('compiler compliance', () => {
$r3$.ɵelementEnd(); $r3$.ɵelementEnd();
} }
if (rf & 2) { if (rf & 2) {
$r3$.ɵflushHooksUpTo(1);
$r3$.ɵelementProperty(1, "forOf", $r3$.ɵbind(ctx.items)); $r3$.ɵelementProperty(1, "forOf", $r3$.ɵbind(ctx.items));
} }
}, },

View File

@ -44,6 +44,7 @@ describe('compiler compliance: bindings', () => {
$i0$.ɵelementEnd(); $i0$.ɵelementEnd();
} }
if (rf & 2) { if (rf & 2) {
$r3$.ɵflushHooksUpTo(1);
$i0$.ɵtextBinding(1, $i0$.ɵinterpolation1("Hello ", $ctx$.name, "")); $i0$.ɵtextBinding(1, $i0$.ɵinterpolation1("Hello ", $ctx$.name, ""));
} }
}`; }`;
@ -473,6 +474,7 @@ describe('compiler compliance: bindings', () => {
} }
if (rf & 2) { if (rf & 2) {
const $_r0$ = $i0$.ɵreference(1); const $_r0$ = $i0$.ɵreference(1);
$r3$.ɵflushHooksUpTo(4);
$i0$.ɵtextBinding(4, $i0$.ɵinterpolation1(" ", $_r0$.id, " ")); $i0$.ɵtextBinding(4, $i0$.ɵinterpolation1(" ", $_r0$.id, " "));
} }
} }

View File

@ -369,6 +369,7 @@ describe('i18n support in the view compiler', () => {
$r3$.ɵi18nExp($r3$.ɵbind($r3$.ɵpipeBind1(1, 0, ctx.valueA))); $r3$.ɵi18nExp($r3$.ɵbind($r3$.ɵpipeBind1(1, 0, ctx.valueA)));
$r3$.ɵi18nExp($r3$.ɵbind(ctx.valueB)); $r3$.ɵi18nExp($r3$.ɵbind(ctx.valueB));
$r3$.ɵi18nApply(2); $r3$.ɵi18nApply(2);
$r3$.ɵflushHooksUpTo(3);
$r3$.ɵi18nExp($r3$.ɵbind(ctx.valueA)); $r3$.ɵi18nExp($r3$.ɵbind(ctx.valueA));
$r3$.ɵi18nExp($r3$.ɵbind(ctx.valueB)); $r3$.ɵi18nExp($r3$.ɵbind(ctx.valueB));
$r3$.ɵi18nExp($r3$.ɵbind((ctx.valueA + ctx.valueB))); $r3$.ɵi18nExp($r3$.ɵbind((ctx.valueA + ctx.valueB)));
@ -436,6 +437,7 @@ describe('i18n support in the view compiler', () => {
} }
if (rf & 2) { if (rf & 2) {
const $outer_r1$ = ctx.$implicit; const $outer_r1$ = ctx.$implicit;
$r3$.ɵflushHooksUpTo(1);
$r3$.ɵi18nExp($r3$.ɵbind($r3$.ɵpipeBind1(2, 0, $outer_r1$))); $r3$.ɵi18nExp($r3$.ɵbind($r3$.ɵpipeBind1(2, 0, $outer_r1$)));
$r3$.ɵi18nApply(3); $r3$.ɵi18nApply(3);
} }
@ -525,6 +527,7 @@ describe('i18n support in the view compiler', () => {
$r3$.ɵi18nExp($r3$.ɵbind($r3$.ɵpipeBind1(1, 0, ctx.valueA))); $r3$.ɵi18nExp($r3$.ɵbind($r3$.ɵpipeBind1(1, 0, ctx.valueA)));
$r3$.ɵi18nExp($r3$.ɵbind(ctx.valueB)); $r3$.ɵi18nExp($r3$.ɵbind(ctx.valueB));
$r3$.ɵi18nApply(2); $r3$.ɵi18nApply(2);
$r3$.ɵflushHooksUpTo(3);
$r3$.ɵi18nExp($r3$.ɵbind(ctx.valueA)); $r3$.ɵi18nExp($r3$.ɵbind(ctx.valueA));
$r3$.ɵi18nExp($r3$.ɵbind(ctx.valueB)); $r3$.ɵi18nExp($r3$.ɵbind(ctx.valueB));
$r3$.ɵi18nExp($r3$.ɵbind((ctx.valueA + ctx.valueB))); $r3$.ɵi18nExp($r3$.ɵbind((ctx.valueA + ctx.valueB)));
@ -565,6 +568,7 @@ describe('i18n support in the view compiler', () => {
} }
if (rf & 2) { if (rf & 2) {
const $outer_r1$ = ctx.$implicit; const $outer_r1$ = ctx.$implicit;
$r3$.ɵflushHooksUpTo(1);
$r3$.ɵi18nExp($r3$.ɵbind($r3$.ɵpipeBind1(2, 0, $outer_r1$))); $r3$.ɵi18nExp($r3$.ɵbind($r3$.ɵpipeBind1(2, 0, $outer_r1$)));
$r3$.ɵi18nApply(3); $r3$.ɵi18nApply(3);
} }
@ -730,6 +734,7 @@ describe('i18n support in the view compiler', () => {
$r3$.ɵelementEnd(); $r3$.ɵelementEnd();
} }
if (rf & 2) { if (rf & 2) {
$r3$.ɵflushHooksUpTo(1);
$r3$.ɵi18nExp($r3$.ɵbind(ctx.valueA)); $r3$.ɵi18nExp($r3$.ɵbind(ctx.valueA));
$r3$.ɵi18nApply(1); $r3$.ɵi18nApply(1);
} }
@ -756,6 +761,7 @@ describe('i18n support in the view compiler', () => {
$r3$.ɵelementEnd(); $r3$.ɵelementEnd();
} }
if (rf & 2) { if (rf & 2) {
$r3$.ɵflushHooksUpTo(1);
$r3$.ɵi18nExp($r3$.ɵbind(ctx.valueA)); $r3$.ɵi18nExp($r3$.ɵbind(ctx.valueA));
$r3$.ɵi18nApply(1); $r3$.ɵi18nApply(1);
} }
@ -786,6 +792,7 @@ describe('i18n support in the view compiler', () => {
$r3$.ɵelementEnd(); $r3$.ɵelementEnd();
} }
if (rf & 2) { if (rf & 2) {
$r3$.ɵflushHooksUpTo(1);
$r3$.ɵi18nExp($r3$.ɵbind($r3$.ɵpipeBind1(2, 2, ctx.valueA))); $r3$.ɵi18nExp($r3$.ɵbind($r3$.ɵpipeBind1(2, 2, ctx.valueA)));
$r3$.ɵi18nExp($r3$.ɵbind(((ctx.valueA == null) ? null : ((ctx.valueA.a == null) ? null : ctx.valueA.a.b)))); $r3$.ɵi18nExp($r3$.ɵbind(((ctx.valueA == null) ? null : ((ctx.valueA.a == null) ? null : ctx.valueA.a.b))));
$r3$.ɵi18nApply(1); $r3$.ɵi18nApply(1);
@ -829,10 +836,13 @@ describe('i18n support in the view compiler', () => {
$r3$.ɵelementEnd(); $r3$.ɵelementEnd();
} }
if (rf & 2) { if (rf & 2) {
$r3$.ɵflushHooksUpTo(1);
$r3$.ɵi18nExp($r3$.ɵbind(ctx.one)); $r3$.ɵi18nExp($r3$.ɵbind(ctx.one));
$r3$.ɵi18nApply(1); $r3$.ɵi18nApply(1);
$r3$.ɵflushHooksUpTo(3);
$r3$.ɵi18nExp($r3$.ɵbind($r3$.ɵpipeBind1(4, 3, ctx.two))); $r3$.ɵi18nExp($r3$.ɵbind($r3$.ɵpipeBind1(4, 3, ctx.two)));
$r3$.ɵi18nApply(3); $r3$.ɵi18nApply(3);
$r3$.ɵflushHooksUpTo(6);
$r3$.ɵi18nExp($r3$.ɵbind(((ctx.three + ctx.four) + ctx.five))); $r3$.ɵi18nExp($r3$.ɵbind(((ctx.three + ctx.four) + ctx.five)));
$r3$.ɵi18nApply(6); $r3$.ɵi18nApply(6);
} }
@ -897,8 +907,10 @@ describe('i18n support in the view compiler', () => {
$r3$.ɵelementEnd(); $r3$.ɵelementEnd();
} }
if (rf & 2) { if (rf & 2) {
$r3$.ɵflushHooksUpTo(1);
$r3$.ɵi18nExp($r3$.ɵbind(ctx.one)); $r3$.ɵi18nExp($r3$.ɵbind(ctx.one));
$r3$.ɵi18nApply(1); $r3$.ɵi18nApply(1);
$r3$.ɵflushHooksUpTo(4);
$r3$.ɵi18nExp($r3$.ɵbind($r3$.ɵpipeBind1(5, 3, ctx.two))); $r3$.ɵi18nExp($r3$.ɵbind($r3$.ɵpipeBind1(5, 3, ctx.two)));
$r3$.ɵi18nExp($r3$.ɵbind(ctx.nestedInBlockTwo)); $r3$.ɵi18nExp($r3$.ɵbind(ctx.nestedInBlockTwo));
$r3$.ɵi18nApply(4); $r3$.ɵi18nApply(4);
@ -965,11 +977,13 @@ describe('i18n support in the view compiler', () => {
$r3$.ɵelementEnd(); $r3$.ɵelementEnd();
} }
if (rf & 2) { if (rf & 2) {
$r3$.ɵflushHooksUpTo(2);
$r3$.ɵi18nExp($r3$.ɵbind(ctx.valueB)); $r3$.ɵi18nExp($r3$.ɵbind(ctx.valueB));
$r3$.ɵi18nExp($r3$.ɵbind(ctx.valueC)); $r3$.ɵi18nExp($r3$.ɵbind(ctx.valueC));
$r3$.ɵi18nApply(3); $r3$.ɵi18nApply(3);
$r3$.ɵi18nExp($r3$.ɵbind(ctx.valueA)); $r3$.ɵi18nExp($r3$.ɵbind(ctx.valueA));
$r3$.ɵi18nApply(1); $r3$.ɵi18nApply(1);
$r3$.ɵflushHooksUpTo(7);
$r3$.ɵi18nExp($r3$.ɵbind(ctx.valueE)); $r3$.ɵi18nExp($r3$.ɵbind(ctx.valueE));
$r3$.ɵi18nApply(8); $r3$.ɵi18nApply(8);
$r3$.ɵi18nExp($r3$.ɵbind($r3$.ɵpipeBind1(6, 5, ctx.valueD))); $r3$.ɵi18nExp($r3$.ɵbind($r3$.ɵpipeBind1(6, 5, ctx.valueD)));
@ -1018,6 +1032,7 @@ describe('i18n support in the view compiler', () => {
} }
if (rf & 2) { if (rf & 2) {
const $ctx_r0$ = $r3$.ɵnextContext(); const $ctx_r0$ = $r3$.ɵnextContext();
$r3$.ɵflushHooksUpTo(2);
$r3$.ɵi18nExp($r3$.ɵbind($ctx_r0$.valueA)); $r3$.ɵi18nExp($r3$.ɵbind($ctx_r0$.valueA));
$r3$.ɵi18nExp($r3$.ɵbind($r3$.ɵpipeBind1(4, 2, $ctx_r0$.valueB))); $r3$.ɵi18nExp($r3$.ɵbind($r3$.ɵpipeBind1(4, 2, $ctx_r0$.valueB)));
$r3$.ɵi18nApply(2); $r3$.ɵi18nApply(2);
@ -1034,6 +1049,7 @@ describe('i18n support in the view compiler', () => {
$r3$.ɵelementEnd(); $r3$.ɵelementEnd();
} }
if (rf & 2) { if (rf & 2) {
$r3$.ɵflushHooksUpTo(2);
$r3$.ɵelementProperty(2, "ngIf", $r3$.ɵbind(ctx.visible)); $r3$.ɵelementProperty(2, "ngIf", $r3$.ɵbind(ctx.visible));
} }
} }
@ -1083,7 +1099,9 @@ describe('i18n support in the view compiler', () => {
$r3$.ɵtemplate(2, MyComponent_img_2_Template, 2, 1, "img", $_c1$); $r3$.ɵtemplate(2, MyComponent_img_2_Template, 2, 1, "img", $_c1$);
} }
if (rf & 2) { if (rf & 2) {
$r3$.ɵflushHooksUpTo(1);
$r3$.ɵelementProperty(1, "ngIf", $r3$.ɵbind(ctx.visible)); $r3$.ɵelementProperty(1, "ngIf", $r3$.ɵbind(ctx.visible));
$r3$.ɵflushHooksUpTo(2);
$r3$.ɵelementProperty(2, "ngIf", $r3$.ɵbind(ctx.visible)); $r3$.ɵelementProperty(2, "ngIf", $r3$.ɵbind(ctx.visible));
} }
} }
@ -1147,6 +1165,7 @@ describe('i18n support in the view compiler', () => {
} }
if (rf & 2) { if (rf & 2) {
const $ctx_r0$ = $r3$.ɵnextContext(); const $ctx_r0$ = $r3$.ɵnextContext();
$r3$.ɵflushHooksUpTo(4);
$r3$.ɵelementProperty(4, "ngIf", $r3$.ɵbind($ctx_r0$.exists)); $r3$.ɵelementProperty(4, "ngIf", $r3$.ɵbind($ctx_r0$.exists));
$r3$.ɵi18nExp($r3$.ɵbind($ctx_r0$.valueA)); $r3$.ɵi18nExp($r3$.ɵbind($ctx_r0$.valueA));
$r3$.ɵi18nExp($r3$.ɵbind($r3$.ɵpipeBind1(3, 3, $ctx_r0$.valueB))); $r3$.ɵi18nExp($r3$.ɵbind($r3$.ɵpipeBind1(3, 3, $ctx_r0$.valueB)));
@ -1196,7 +1215,9 @@ describe('i18n support in the view compiler', () => {
$r3$.ɵelementEnd(); $r3$.ɵelementEnd();
} }
if (rf & 2) { if (rf & 2) {
$r3$.ɵflushHooksUpTo(2);
$r3$.ɵelementProperty(2, "ngIf", $r3$.ɵbind(ctx.visible)); $r3$.ɵelementProperty(2, "ngIf", $r3$.ɵbind(ctx.visible));
$r3$.ɵflushHooksUpTo(3);
$r3$.ɵelementProperty(3, "ngIf", $r3$.ɵbind(!ctx.visible)); $r3$.ɵelementProperty(3, "ngIf", $r3$.ɵbind(!ctx.visible));
} }
} }
@ -1228,6 +1249,7 @@ describe('i18n support in the view compiler', () => {
} }
if (rf & 2) { if (rf & 2) {
const $ctx_r0$ = $r3$.ɵnextContext(); const $ctx_r0$ = $r3$.ɵnextContext();
$r3$.ɵflushHooksUpTo(1);
$r3$.ɵi18nExp($r3$.ɵbind($ctx_r0$.valueA)); $r3$.ɵi18nExp($r3$.ɵbind($ctx_r0$.valueA));
$r3$.ɵi18nApply(1); $r3$.ɵi18nApply(1);
} }
@ -1290,6 +1312,7 @@ describe('i18n support in the view compiler', () => {
$r3$.ɵelementEnd(); $r3$.ɵelementEnd();
} }
if (rf & 2) { if (rf & 2) {
$r3$.ɵflushHooksUpTo(1);
$r3$.ɵi18nExp($r3$.ɵbind(ctx.age)); $r3$.ɵi18nExp($r3$.ɵbind(ctx.age));
$r3$.ɵi18nApply(1); $r3$.ɵi18nApply(1);
} }
@ -1378,6 +1401,7 @@ describe('i18n support in the view compiler', () => {
$r3$.ɵelementContainerEnd(); $r3$.ɵelementContainerEnd();
} }
if (rf & 2) { if (rf & 2) {
$r3$.ɵflushHooksUpTo(1);
$r3$.ɵi18nExp($r3$.ɵbind($r3$.ɵpipeBind1(2, 1, ctx.valueA))); $r3$.ɵi18nExp($r3$.ɵbind($r3$.ɵpipeBind1(2, 1, ctx.valueA)));
$r3$.ɵi18nApply(1); $r3$.ɵi18nApply(1);
} }
@ -1462,6 +1486,7 @@ describe('i18n support in the view compiler', () => {
$r3$.ɵelementEnd(); $r3$.ɵelementEnd();
} }
if (rf & 2) { if (rf & 2) {
$r3$.ɵflushHooksUpTo(1);
$r3$.ɵi18nExp($r3$.ɵbind($r3$.ɵpipeBind1(4, 1, ctx.valueB))); $r3$.ɵi18nExp($r3$.ɵbind($r3$.ɵpipeBind1(4, 1, ctx.valueB)));
$r3$.ɵi18nApply(1); $r3$.ɵi18nApply(1);
} }
@ -1507,6 +1532,7 @@ describe('i18n support in the view compiler', () => {
$r3$.ɵelementContainerEnd(); $r3$.ɵelementContainerEnd();
} }
if (rf & 2) { if (rf & 2) {
$r3$.ɵflushHooksUpTo(2);
$r3$.ɵi18nExp($r3$.ɵbind(ctx.age)); $r3$.ɵi18nExp($r3$.ɵbind(ctx.age));
$r3$.ɵi18nApply(2); $r3$.ɵi18nApply(2);
} }
@ -1628,6 +1654,7 @@ describe('i18n support in the view compiler', () => {
$r3$.ɵtemplate(2, MyComponent_ng_template_2_Template, 1, 1, "ng-template"); $r3$.ɵtemplate(2, MyComponent_ng_template_2_Template, 1, 1, "ng-template");
} }
if (rf & 2) { if (rf & 2) {
$r3$.ɵflushHooksUpTo(1);
$r3$.ɵi18nExp($r3$.ɵbind(ctx.gender)); $r3$.ɵi18nExp($r3$.ɵbind(ctx.gender));
$r3$.ɵi18nApply(1); $r3$.ɵi18nApply(1);
} }
@ -1771,6 +1798,7 @@ describe('i18n support in the view compiler', () => {
$r3$.ɵelementEnd(); $r3$.ɵelementEnd();
} }
if (rf & 2) { if (rf & 2) {
$r3$.ɵflushHooksUpTo(1);
$r3$.ɵi18nExp($r3$.ɵbind(ctx.gender)); $r3$.ɵi18nExp($r3$.ɵbind(ctx.gender));
$r3$.ɵi18nApply(1); $r3$.ɵi18nApply(1);
} }
@ -1850,6 +1878,7 @@ describe('i18n support in the view compiler', () => {
} }
if (rf & 2) { if (rf & 2) {
const $ctx_r0$ = $r3$.ɵnextContext(); const $ctx_r0$ = $r3$.ɵnextContext();
$r3$.ɵflushHooksUpTo(1);
$r3$.ɵi18nExp($r3$.ɵbind($ctx_r0$.age)); $r3$.ɵi18nExp($r3$.ɵbind($ctx_r0$.age));
$r3$.ɵi18nApply(1); $r3$.ɵi18nApply(1);
} }
@ -1871,6 +1900,7 @@ describe('i18n support in the view compiler', () => {
} }
if (rf & 2) { if (rf & 2) {
const $ctx_r1$ = $r3$.ɵnextContext(); const $ctx_r1$ = $r3$.ɵnextContext();
$r3$.ɵflushHooksUpTo(2);
$r3$.ɵi18nExp($r3$.ɵbind($ctx_r1$.count)); $r3$.ɵi18nExp($r3$.ɵbind($ctx_r1$.count));
$r3$.ɵi18nExp($r3$.ɵbind($ctx_r1$.count)); $r3$.ɵi18nExp($r3$.ɵbind($ctx_r1$.count));
$r3$.ɵi18nApply(2); $r3$.ɵi18nApply(2);
@ -1888,9 +1918,12 @@ describe('i18n support in the view compiler', () => {
$r3$.ɵtemplate(3, MyComponent_div_3_Template, 4, 2, "div", $_c0$); $r3$.ɵtemplate(3, MyComponent_div_3_Template, 4, 2, "div", $_c0$);
} }
if (rf & 2) { if (rf & 2) {
$r3$.ɵflushHooksUpTo(1);
$r3$.ɵi18nExp($r3$.ɵbind(ctx.gender)); $r3$.ɵi18nExp($r3$.ɵbind(ctx.gender));
$r3$.ɵi18nApply(1); $r3$.ɵi18nApply(1);
$r3$.ɵflushHooksUpTo(2);
$r3$.ɵelementProperty(2, "ngIf", $r3$.ɵbind(ctx.visible)); $r3$.ɵelementProperty(2, "ngIf", $r3$.ɵbind(ctx.visible));
$r3$.ɵflushHooksUpTo(3);
$r3$.ɵelementProperty(3, "ngIf", $r3$.ɵbind(ctx.available)); $r3$.ɵelementProperty(3, "ngIf", $r3$.ɵbind(ctx.available));
} }
} }
@ -1917,6 +1950,7 @@ describe('i18n support in the view compiler', () => {
$r3$.ɵelementEnd(); $r3$.ɵelementEnd();
} }
if (rf & 2) { if (rf & 2) {
$r3$.ɵflushHooksUpTo(1);
$r3$.ɵi18nExp($r3$.ɵbind(ctx.age)); $r3$.ɵi18nExp($r3$.ɵbind(ctx.age));
$r3$.ɵi18nExp($r3$.ɵbind(ctx.other)); $r3$.ɵi18nExp($r3$.ɵbind(ctx.other));
$r3$.ɵi18nApply(1); $r3$.ɵi18nApply(1);
@ -2004,6 +2038,7 @@ describe('i18n support in the view compiler', () => {
$r3$.ɵelementEnd(); $r3$.ɵelementEnd();
} }
if (rf & 2) { if (rf & 2) {
$r3$.ɵflushHooksUpTo(1);
$r3$.ɵi18nExp($r3$.ɵbind(ctx.gender)); $r3$.ɵi18nExp($r3$.ɵbind(ctx.gender));
$r3$.ɵi18nExp($r3$.ɵbind(((ctx.ageA + ctx.ageB) + ctx.ageC))); $r3$.ɵi18nExp($r3$.ɵbind(((ctx.ageA + ctx.ageB) + ctx.ageC)));
$r3$.ɵi18nApply(1); $r3$.ɵi18nApply(1);
@ -2045,6 +2080,7 @@ describe('i18n support in the view compiler', () => {
$r3$.ɵelementEnd(); $r3$.ɵelementEnd();
} }
if (rf & 2) { if (rf & 2) {
$r3$.ɵflushHooksUpTo(1);
$r3$.ɵi18nExp($r3$.ɵbind(ctx.gender)); $r3$.ɵi18nExp($r3$.ɵbind(ctx.gender));
$r3$.ɵi18nExp($r3$.ɵbind(ctx.age)); $r3$.ɵi18nExp($r3$.ɵbind(ctx.age));
$r3$.ɵi18nApply(1); $r3$.ɵi18nApply(1);
@ -2116,6 +2152,7 @@ describe('i18n support in the view compiler', () => {
$r3$.ɵelementEnd(); $r3$.ɵelementEnd();
} }
if (rf & 2) { if (rf & 2) {
$r3$.ɵflushHooksUpTo(3);
$r3$.ɵelementProperty(3, "ngIf", $r3$.ɵbind(ctx.visible)); $r3$.ɵelementProperty(3, "ngIf", $r3$.ɵbind(ctx.visible));
$r3$.ɵi18nExp($r3$.ɵbind(ctx.gender)); $r3$.ɵi18nExp($r3$.ɵbind(ctx.gender));
$r3$.ɵi18nExp($r3$.ɵbind(ctx.gender)); $r3$.ɵi18nExp($r3$.ɵbind(ctx.gender));
@ -2157,6 +2194,7 @@ describe('i18n support in the view compiler', () => {
$r3$.ɵelementEnd(); $r3$.ɵelementEnd();
} }
if (rf & 2) { if (rf & 2) {
$r3$.ɵflushHooksUpTo(1);
$r3$.ɵi18nExp($r3$.ɵbind(ctx.age)); $r3$.ɵi18nExp($r3$.ɵbind(ctx.age));
$r3$.ɵi18nExp($r3$.ɵbind(ctx.gender)); $r3$.ɵi18nExp($r3$.ɵbind(ctx.gender));
$r3$.ɵi18nApply(1); $r3$.ɵi18nApply(1);
@ -2220,6 +2258,7 @@ describe('i18n support in the view compiler', () => {
$r3$.ɵelementEnd(); $r3$.ɵelementEnd();
} }
if (rf & 2) { if (rf & 2) {
$r3$.ɵflushHooksUpTo(2);
$r3$.ɵelementProperty(2, "ngIf", $r3$.ɵbind(ctx.ageVisible)); $r3$.ɵelementProperty(2, "ngIf", $r3$.ɵbind(ctx.ageVisible));
$r3$.ɵi18nExp($r3$.ɵbind(ctx.gender)); $r3$.ɵi18nExp($r3$.ɵbind(ctx.gender));
$r3$.ɵi18nApply(1); $r3$.ɵi18nApply(1);
@ -2286,6 +2325,7 @@ describe('i18n support in the view compiler', () => {
$r3$.ɵelementEnd(); $r3$.ɵelementEnd();
} }
if (rf & 2) { if (rf & 2) {
$r3$.ɵflushHooksUpTo(2);
$r3$.ɵelementProperty(2, "ngIf", $r3$.ɵbind(ctx.ageVisible)); $r3$.ɵelementProperty(2, "ngIf", $r3$.ɵbind(ctx.ageVisible));
$r3$.ɵi18nExp($r3$.ɵbind(ctx.gender)); $r3$.ɵi18nExp($r3$.ɵbind(ctx.gender));
$r3$.ɵi18nExp($r3$.ɵbind(ctx.weight)); $r3$.ɵi18nExp($r3$.ɵbind(ctx.weight));
@ -2328,6 +2368,7 @@ describe('i18n support in the view compiler', () => {
$r3$.ɵelementEnd(); $r3$.ɵelementEnd();
} }
if (rf & 2) { if (rf & 2) {
$r3$.ɵflushHooksUpTo(1);
$r3$.ɵi18nExp($r3$.ɵbind(ctx.gender)); $r3$.ɵi18nExp($r3$.ɵbind(ctx.gender));
$r3$.ɵi18nExp($r3$.ɵbind(ctx.weight)); $r3$.ɵi18nExp($r3$.ɵbind(ctx.weight));
$r3$.ɵi18nExp($r3$.ɵbind(ctx.height)); $r3$.ɵi18nExp($r3$.ɵbind(ctx.height));

View File

@ -227,7 +227,9 @@ describe('compiler compliance: styling', () => {
} }
if (rf & 2) { if (rf & 2) {
$r3$.ɵelementProperty(0, "@foo", $r3$.ɵbind(ctx.exp)); $r3$.ɵelementProperty(0, "@foo", $r3$.ɵbind(ctx.exp));
$r3$.ɵflushHooksUpTo(1);
$r3$.ɵelementProperty(1, "@bar", $r3$.ɵbind(undefined)); $r3$.ɵelementProperty(1, "@bar", $r3$.ɵbind(undefined));
$r3$.ɵflushHooksUpTo(2);
$r3$.ɵelementProperty(2, "@baz", $r3$.ɵbind(undefined)); $r3$.ɵelementProperty(2, "@baz", $r3$.ɵbind(undefined));
} }
}, },
@ -930,6 +932,7 @@ describe('compiler compliance: styling', () => {
$r3$.ɵelementStyleProp(0, 1, $r3$.ɵpipeBind2(3, 7, $ctx$.bazExp, 4000)); $r3$.ɵelementStyleProp(0, 1, $r3$.ɵpipeBind2(3, 7, $ctx$.bazExp, 4000));
$r3$.ɵelementClassProp(0, 0, $r3$.ɵpipeBind2(4, 10, $ctx$.fooExp, 2000)); $r3$.ɵelementClassProp(0, 0, $r3$.ɵpipeBind2(4, 10, $ctx$.fooExp, 2000));
$r3$.ɵelementStylingApply(0); $r3$.ɵelementStylingApply(0);
$r3$.ɵflushHooksUpTo(5);
$r3$.ɵtextBinding(5, $r3$.ɵinterpolation1(" ", $ctx$.item, "")); $r3$.ɵtextBinding(5, $r3$.ɵinterpolation1(" ", $ctx$.item, ""));
} }
} }

View File

@ -75,6 +75,7 @@ describe('compiler compliance: template', () => {
const $outer1$ = $i0$.ɵnextContext().$implicit; const $outer1$ = $i0$.ɵnextContext().$implicit;
const $myComp1$ = $i0$.ɵnextContext(); const $myComp1$ = $i0$.ɵnextContext();
$i0$.ɵelementProperty(0, "title", $i0$.ɵbind($myComp1$.format($outer1$, $middle1$, $inner1$, $myComp1$.component))); $i0$.ɵelementProperty(0, "title", $i0$.ɵbind($myComp1$.format($outer1$, $middle1$, $inner1$, $myComp1$.component)));
$r3$.ɵflushHooksUpTo(1);
$i0$.ɵtextBinding(1, $i0$.ɵinterpolation1(" ", $myComp1$.format($outer1$, $middle1$, $inner1$, $myComp1$.component), " ")); $i0$.ɵtextBinding(1, $i0$.ɵinterpolation1(" ", $myComp1$.format($outer1$, $middle1$, $inner1$, $myComp1$.component), " "));
} }
} }
@ -87,6 +88,7 @@ describe('compiler compliance: template', () => {
} }
if (rf & 2) { if (rf & 2) {
const $myComp2$ = $i0$.ɵnextContext(2); const $myComp2$ = $i0$.ɵnextContext(2);
$r3$.ɵflushHooksUpTo(1);
$i0$.ɵelementProperty(1, "ngForOf", $i0$.ɵbind($myComp2$.items)); $i0$.ɵelementProperty(1, "ngForOf", $i0$.ɵbind($myComp2$.items));
} }
} }
@ -99,6 +101,7 @@ describe('compiler compliance: template', () => {
} }
if (rf & 2) { if (rf & 2) {
const $outer2$ = ctx.$implicit; const $outer2$ = ctx.$implicit;
$r3$.ɵflushHooksUpTo(1);
$i0$.ɵelementProperty(1, "ngForOf", $i0$.ɵbind($outer2$.items)); $i0$.ɵelementProperty(1, "ngForOf", $i0$.ɵbind($outer2$.items));
} }
} }
@ -207,6 +210,7 @@ describe('compiler compliance: template', () => {
if (rf & 2) { if (rf & 2) {
const $item$ = ctx.$implicit; const $item$ = ctx.$implicit;
const $i$ = ctx.index; const $i$ = ctx.index;
$r3$.ɵflushHooksUpTo(1);
$i0$.ɵtextBinding(1, $i0$.ɵinterpolation2(" ", $i$, " - ", $item$, " ")); $i0$.ɵtextBinding(1, $i0$.ɵinterpolation2(" ", $i$, " - ", $item$, " "));
} }
} }
@ -262,6 +266,7 @@ describe('compiler compliance: template', () => {
const $div$ = $i0$.ɵnextContext(); const $div$ = $i0$.ɵnextContext();
const $i$ = $div$.index; const $i$ = $div$.index;
const $item$ = $div$.$implicit; const $item$ = $div$.$implicit;
$r3$.ɵflushHooksUpTo(1);
$i0$.ɵtextBinding(1, $i0$.ɵinterpolation2(" ", $i$, " - ", $item$, " ")); $i0$.ɵtextBinding(1, $i0$.ɵinterpolation2(" ", $i$, " - ", $item$, " "));
} }
} }
@ -274,6 +279,7 @@ describe('compiler compliance: template', () => {
} }
if (rf & 2) { if (rf & 2) {
const $app$ = $i0$.ɵnextContext(); const $app$ = $i0$.ɵnextContext();
$r3$.ɵflushHooksUpTo(1);
$i0$.ɵelementProperty(1, "ngIf", $i0$.ɵbind($app$.showing)); $i0$.ɵelementProperty(1, "ngIf", $i0$.ɵbind($app$.showing));
} }
} }
@ -330,6 +336,7 @@ describe('compiler compliance: template', () => {
if (rf & 2) { if (rf & 2) {
const $middle$ = $i0$.ɵnextContext().$implicit; const $middle$ = $i0$.ɵnextContext().$implicit;
const $myComp$ = $i0$.ɵnextContext(2); const $myComp$ = $i0$.ɵnextContext(2);
$r3$.ɵflushHooksUpTo(1);
$i0$.ɵtextBinding(1, $i0$.ɵinterpolation2(" ", $middle$.value, " - ", $myComp$.name, " ")); $i0$.ɵtextBinding(1, $i0$.ɵinterpolation2(" ", $middle$.value, " - ", $myComp$.name, " "));
} }
} }
@ -342,6 +349,7 @@ describe('compiler compliance: template', () => {
} }
if (rf & 2) { if (rf & 2) {
const $middle$ = ctx.$implicit; const $middle$ = ctx.$implicit;
$r3$.ɵflushHooksUpTo(1);
$i0$.ɵelementProperty(1, "ngForOf", $i0$.ɵbind($middle$.items)); $i0$.ɵelementProperty(1, "ngForOf", $i0$.ɵbind($middle$.items));
} }
} }
@ -354,6 +362,7 @@ describe('compiler compliance: template', () => {
} }
if (rf & 2) { if (rf & 2) {
const $outer$ = ctx.$implicit; const $outer$ = ctx.$implicit;
$r3$.ɵflushHooksUpTo(1);
$i0$.ɵelementProperty(1, "ngForOf", $i0$.ɵbind($outer$.items)); $i0$.ɵelementProperty(1, "ngForOf", $i0$.ɵbind($outer$.items));
} }
} }

View File

@ -31,6 +31,8 @@ export class Identifiers {
static elementProperty: o.ExternalReference = {name: 'ɵelementProperty', moduleName: CORE}; static elementProperty: o.ExternalReference = {name: 'ɵelementProperty', moduleName: CORE};
static flushHooksUpTo: o.ExternalReference = {name: 'ɵflushHooksUpTo', moduleName: CORE};
static componentHostSyntheticProperty: static componentHostSyntheticProperty:
o.ExternalReference = {name: 'ɵcomponentHostSyntheticProperty', moduleName: CORE}; o.ExternalReference = {name: 'ɵcomponentHostSyntheticProperty', moduleName: CORE};

View File

@ -120,6 +120,11 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
* all local refs and context variables are available for matching. * all local refs and context variables are available for matching.
*/ */
private _updateCodeFns: (() => o.Statement)[] = []; private _updateCodeFns: (() => o.Statement)[] = [];
/**
* Memorizes the last node index for which a flushHooksUpTo instruction has been generated.
* Initialized to 0 to avoid generating a useless flushHooksUpTo(0).
*/
private _lastNodeIndexWithFlush: number = 0;
/** Temporary variable declarations generated from visiting pipes, literals, etc. */ /** Temporary variable declarations generated from visiting pipes, literals, etc. */
private _tempVariables: o.Statement[] = []; private _tempVariables: o.Statement[] = [];
/** /**
@ -451,10 +456,10 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
if (bindings.size) { if (bindings.size) {
bindings.forEach(binding => { bindings.forEach(binding => {
this.updateInstruction( this.updateInstruction(
span, R3.i18nExp, index, span, R3.i18nExp,
() => [this.convertPropertyBinding(o.variable(CONTEXT_NAME), binding)]); () => [this.convertPropertyBinding(o.variable(CONTEXT_NAME), binding)]);
}); });
this.updateInstruction(span, R3.i18nApply, [o.literal(index)]); this.updateInstruction(index, span, R3.i18nApply, [o.literal(index)]);
} }
if (!selfClosing) { if (!selfClosing) {
this.creationInstruction(span, R3.i18nEnd); this.creationInstruction(span, R3.i18nEnd);
@ -639,7 +644,7 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
converted.expressions.forEach(expression => { converted.expressions.forEach(expression => {
hasBindings = true; hasBindings = true;
const binding = this.convertExpressionBinding(implicit, expression); const binding = this.convertExpressionBinding(implicit, expression);
this.updateInstruction(element.sourceSpan, R3.i18nExp, [binding]); this.updateInstruction(elementIndex, element.sourceSpan, R3.i18nExp, [binding]);
}); });
} }
} }
@ -649,7 +654,7 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
const args = this.constantPool.getConstLiteral(o.literalArr(i18nAttrArgs), true); const args = this.constantPool.getConstLiteral(o.literalArr(i18nAttrArgs), true);
this.creationInstruction(element.sourceSpan, R3.i18nAttributes, [index, args]); this.creationInstruction(element.sourceSpan, R3.i18nAttributes, [index, args]);
if (hasBindings) { if (hasBindings) {
this.updateInstruction(element.sourceSpan, R3.i18nApply, [index]); this.updateInstruction(elementIndex, element.sourceSpan, R3.i18nApply, [index]);
} }
} }
} }
@ -711,7 +716,7 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
const hasValue = value instanceof LiteralPrimitive ? !!value.value : true; const hasValue = value instanceof LiteralPrimitive ? !!value.value : true;
this.allocateBindingSlots(value); this.allocateBindingSlots(value);
const bindingName = prepareSyntheticPropertyName(input.name); const bindingName = prepareSyntheticPropertyName(input.name);
this.updateInstruction(input.sourceSpan, R3.elementProperty, () => { this.updateInstruction(elementIndex, input.sourceSpan, R3.elementProperty, () => {
return [ return [
o.literal(elementIndex), o.literal(bindingName), o.literal(elementIndex), o.literal(bindingName),
(hasValue ? this.convertPropertyBinding(implicit, value) : emptyValueBindInstruction) (hasValue ? this.convertPropertyBinding(implicit, value) : emptyValueBindInstruction)
@ -737,7 +742,7 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
} }
} }
this.allocateBindingSlots(value); this.allocateBindingSlots(value);
this.updateInstruction(input.sourceSpan, instruction, () => { this.updateInstruction(elementIndex, input.sourceSpan, instruction, () => {
return [ return [
o.literal(elementIndex), o.literal(attrName), o.literal(elementIndex), o.literal(attrName),
this.convertPropertyBinding(implicit, value), ...params this.convertPropertyBinding(implicit, value), ...params
@ -839,7 +844,7 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
template.inputs.forEach(input => { template.inputs.forEach(input => {
const value = input.value.visit(this._valueConverter); const value = input.value.visit(this._valueConverter);
this.allocateBindingSlots(value); this.allocateBindingSlots(value);
this.updateInstruction(template.sourceSpan, R3.elementProperty, () => { this.updateInstruction(templateIndex, template.sourceSpan, R3.elementProperty, () => {
return [ return [
o.literal(templateIndex), o.literal(input.name), o.literal(templateIndex), o.literal(input.name),
this.convertPropertyBinding(context, value) this.convertPropertyBinding(context, value)
@ -880,7 +885,7 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
const value = text.value.visit(this._valueConverter); const value = text.value.visit(this._valueConverter);
this.allocateBindingSlots(value); this.allocateBindingSlots(value);
this.updateInstruction( this.updateInstruction(
text.sourceSpan, R3.textBinding, nodeIndex, text.sourceSpan, R3.textBinding,
() => [o.literal(nodeIndex), this.convertPropertyBinding(o.variable(CONTEXT_NAME), value)]); () => [o.literal(nodeIndex), this.convertPropertyBinding(o.variable(CONTEXT_NAME), value)]);
} }
@ -966,7 +971,7 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
if (createMode) { if (createMode) {
this.creationInstruction(instruction.sourceSpan, instruction.reference, paramsFn); this.creationInstruction(instruction.sourceSpan, instruction.reference, paramsFn);
} else { } else {
this.updateInstruction(instruction.sourceSpan, instruction.reference, paramsFn); this.updateInstruction(-1, instruction.sourceSpan, instruction.reference, paramsFn);
} }
} }
} }
@ -978,8 +983,12 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
} }
private updateInstruction( private updateInstruction(
span: ParseSourceSpan|null, reference: o.ExternalReference, nodeIndex: number, span: ParseSourceSpan|null, reference: o.ExternalReference,
paramsOrFn?: o.Expression[]|(() => o.Expression[])) { paramsOrFn?: o.Expression[]|(() => o.Expression[])) {
if (this._lastNodeIndexWithFlush < nodeIndex) {
this.instructionFn(this._updateCodeFns, span, R3.flushHooksUpTo, [o.literal(nodeIndex)]);
this._lastNodeIndexWithFlush = nodeIndex;
}
this.instructionFn(this._updateCodeFns, span, reference, paramsOrFn || []); this.instructionFn(this._updateCodeFns, span, reference, paramsOrFn || []);
} }

View File

@ -104,6 +104,7 @@ export {
elementStyleProp as ɵelementStyleProp, elementStyleProp as ɵelementStyleProp,
elementStylingApply as ɵelementStylingApply, elementStylingApply as ɵelementStylingApply,
elementClassProp as ɵelementClassProp, elementClassProp as ɵelementClassProp,
flushHooksUpTo as ɵflushHooksUpTo,
textBinding as ɵtextBinding, textBinding as ɵtextBinding,
template as ɵtemplate, template as ɵtemplate,
embeddedViewEnd as ɵembeddedViewEnd, embeddedViewEnd as ɵembeddedViewEnd,

View File

@ -29,7 +29,7 @@ import {renderInitialClasses, renderInitialStyles} from './styling/class_and_sty
import {publishDefaultGlobalUtils} from './util/global_utils'; import {publishDefaultGlobalUtils} from './util/global_utils';
import {defaultScheduler, renderStringify} from './util/misc_utils'; import {defaultScheduler, renderStringify} from './util/misc_utils';
import {getRootContext, getRootView} from './util/view_traversal_utils'; import {getRootContext, getRootView} from './util/view_traversal_utils';
import {readPatchedLView} from './util/view_utils'; import {readPatchedLView, resetPreOrderHookFlags} from './util/view_utils';
@ -142,6 +142,7 @@ export function renderComponent<T>(
refreshDescendantViews(rootView); // creation mode pass refreshDescendantViews(rootView); // creation mode pass
rootView[FLAGS] &= ~LViewFlags.CreationMode; rootView[FLAGS] &= ~LViewFlags.CreationMode;
resetPreOrderHookFlags(rootView);
refreshDescendantViews(rootView); // update mode pass refreshDescendantViews(rootView); // update mode pass
} finally { } finally {
leaveView(oldView); leaveView(oldView);
@ -248,7 +249,7 @@ export function LifecycleHooksFeature(component: any, def: ComponentDef<any>): v
const rootTView = readPatchedLView(component) ![TVIEW]; const rootTView = readPatchedLView(component) ![TVIEW];
const dirIndex = rootTView.data.length - 1; const dirIndex = rootTView.data.length - 1;
registerPreOrderHooks(dirIndex, def, rootTView); registerPreOrderHooks(dirIndex, def, rootTView, -1, -1, -1);
// TODO(misko): replace `as TNode` with createTNode call. (needs refactoring to lose dep on // TODO(misko): replace `as TNode` with createTNode call. (needs refactoring to lose dep on
// LNode). // LNode).
registerPostOrderHooks( registerPostOrderHooks(

View File

@ -10,7 +10,7 @@ import {assertEqual} from '../util/assert';
import {DirectiveDef} from './interfaces/definition'; import {DirectiveDef} from './interfaces/definition';
import {TNode} from './interfaces/node'; import {TNode} from './interfaces/node';
import {FLAGS, HookData, InitPhaseState, LView, LViewFlags, TView} from './interfaces/view'; import {FLAGS, HookData, InitPhaseState, LView, LViewFlags, PREORDER_HOOK_FLAGS, PreOrderHookFlags, TView} from './interfaces/view';
@ -19,34 +19,50 @@ import {FLAGS, HookData, InitPhaseState, LView, LViewFlags, TView} from './inter
* *
* Must be run *only* on the first template pass. * Must be run *only* on the first template pass.
* *
* The TView's hooks arrays are arranged in alternating pairs of directiveIndex and hookFunction, * Sets up the pre-order hooks on the provided `tView`,
* i.e.: `[directiveIndexA, hookFunctionA, directiveIndexB, hookFunctionB, ...]`. For `OnChanges` * see {@link HookData} for details about the data structure.
* hooks, the `directiveIndex` will be *negative*, signaling {@link callHooks} that the
* `hookFunction` must be passed the the appropriate {@link SimpleChanges} object.
* *
* @param directiveIndex The index of the directive in LView * @param directiveIndex The index of the directive in LView
* @param directiveDef The definition containing the hooks to setup in tView * @param directiveDef The definition containing the hooks to setup in tView
* @param tView The current TView * @param tView The current TView
* @param nodeIndex The index of the node to which the directive is attached
* @param initialPreOrderHooksLength the number of pre-order hooks already registered before the
* current process, used to know if the node index has to be added to the array. If it is -1,
* the node index is never added.
* @param initialPreOrderCheckHooksLength same as previous for pre-order check hooks
*/ */
export function registerPreOrderHooks( export function registerPreOrderHooks(
directiveIndex: number, directiveDef: DirectiveDef<any>, tView: TView): void { directiveIndex: number, directiveDef: DirectiveDef<any>, tView: TView, nodeIndex: number,
initialPreOrderHooksLength: number, initialPreOrderCheckHooksLength: number): void {
ngDevMode && ngDevMode &&
assertEqual(tView.firstTemplatePass, true, 'Should only be called on first template pass'); assertEqual(tView.firstTemplatePass, true, 'Should only be called on first template pass');
const {onChanges, onInit, doCheck} = directiveDef; const {onChanges, onInit, doCheck} = directiveDef;
if (initialPreOrderHooksLength >= 0 &&
(!tView.preOrderHooks || initialPreOrderHooksLength === tView.preOrderHooks.length) &&
(onChanges || onInit || doCheck)) {
(tView.preOrderHooks || (tView.preOrderHooks = [])).push(nodeIndex);
}
if (initialPreOrderCheckHooksLength >= 0 &&
(!tView.preOrderCheckHooks ||
initialPreOrderCheckHooksLength === tView.preOrderCheckHooks.length) &&
(onChanges || doCheck)) {
(tView.preOrderCheckHooks || (tView.preOrderCheckHooks = [])).push(nodeIndex);
}
if (onChanges) { if (onChanges) {
(tView.initHooks || (tView.initHooks = [])).push(directiveIndex, onChanges); (tView.preOrderHooks || (tView.preOrderHooks = [])).push(directiveIndex, onChanges);
(tView.checkHooks || (tView.checkHooks = [])).push(directiveIndex, onChanges); (tView.preOrderCheckHooks || (tView.preOrderCheckHooks = [])).push(directiveIndex, onChanges);
} }
if (onInit) { if (onInit) {
(tView.initHooks || (tView.initHooks = [])).push(-directiveIndex, onInit); (tView.preOrderHooks || (tView.preOrderHooks = [])).push(-directiveIndex, onInit);
} }
if (doCheck) { if (doCheck) {
(tView.initHooks || (tView.initHooks = [])).push(directiveIndex, doCheck); (tView.preOrderHooks || (tView.preOrderHooks = [])).push(directiveIndex, doCheck);
(tView.checkHooks || (tView.checkHooks = [])).push(directiveIndex, doCheck); (tView.preOrderCheckHooks || (tView.preOrderCheckHooks = [])).push(directiveIndex, doCheck);
} }
} }
@ -59,9 +75,8 @@ export function registerPreOrderHooks(
* preserve hook execution order. Content, view, and destroy hooks for projected * preserve hook execution order. Content, view, and destroy hooks for projected
* components and directives must be called *before* their hosts. * components and directives must be called *before* their hosts.
* *
* Sets up the content, view, and destroy hooks on the provided `tView` such that * Sets up the content, view, and destroy hooks on the provided `tView`,
* they're added in alternating pairs of directiveIndex and hookFunction, * see {@link HookData} for details about the data structure.
* i.e.: `[directiveIndexA, hookFunctionA, directiveIndexB, hookFunctionB, ...]`
* *
* NOTE: This does not set up `onChanges`, `onInit` or `doCheck`, those are set up * NOTE: This does not set up `onChanges`, `onInit` or `doCheck`, those are set up
* separately at `elementStart`. * separately at `elementStart`.
@ -103,25 +118,49 @@ export function registerPostOrderHooks(tView: TView, tNode: TNode): void {
} }
} }
/**
* Executing hooks requires complex logic as we need to deal with 2 constraints.
*
* 1. Init hooks (ngOnInit, ngAfterContentInit, ngAfterViewInit) must all be executed once and only
* once, across many change detection cycles. This must be true even if some hooks throw, or if
* some recursively trigger a change detection cycle.
* To solve that, it is required to track the state of the execution of these init hooks.
* This is done by storing and maintaining flags in the view: the {@link InitPhaseState},
* and the index within that phase. They can be seen as a cursor in the following structure:
* [[onInit1, onInit2], [afterContentInit1], [afterViewInit1, afterViewInit2, afterViewInit3]]
* They are are stored as flags in LView[FLAGS].
*
* 2. Pre-order hooks can be executed in batches, because of the flushHooksUpTo instruction.
* To be able to pause and resume their execution, we also need some state about the hook's array
* that is being processed:
* - the index of the next hook to be executed
* - the number of init hooks already found in the processed part of the array
* They are are stored as flags in LView[PREORDER_HOOK_FLAGS].
*/
/** /**
* Executes necessary hooks at the start of executing a template. * Executes necessary hooks at the start of executing a template.
* *
* Executes hooks that are to be run during the initialization of a directive such * Executes hooks that are to be run during the initialization of a directive such
* as `onChanges`, `onInit`, and `doCheck`. * as `onChanges`, `onInit`, and `doCheck`.
* *
* Has the side effect of updating the RunInit flag in `lView` to be `0`, so that
* this isn't run a second time.
*
* @param lView The current view * @param lView The current view
* @param tView Static data for the view containing the hooks to be executed * @param tView Static data for the view containing the hooks to be executed
* @param checkNoChangesMode Whether or not we're in checkNoChanges mode. * @param checkNoChangesMode Whether or not we're in checkNoChanges mode.
* @param @param currentNodeIndex 2 cases depending the the value:
* - undefined: execute hooks only from the saved index until the end of the array (pre-order case,
* when flushing the remaining hooks)
* - number: execute hooks only from the saved index until that node index exclusive (pre-order
* case, when executing flushHooksUpTo(number))
*/ */
export function executeInitHooks( export function executePreOrderHooks(
currentView: LView, tView: TView, checkNoChangesMode: boolean): void { currentView: LView, tView: TView, checkNoChangesMode: boolean,
currentNodeIndex: number | undefined): void {
if (!checkNoChangesMode) { if (!checkNoChangesMode) {
executeHooks( executeHooks(
currentView, tView.initHooks, tView.checkHooks, checkNoChangesMode, currentView, tView.preOrderHooks, tView.preOrderCheckHooks, checkNoChangesMode,
InitPhaseState.OnInitHooksToBeRun); InitPhaseState.OnInitHooksToBeRun,
currentNodeIndex !== undefined ? currentNodeIndex : null);
} }
} }
@ -129,24 +168,33 @@ export function executeInitHooks(
* Executes hooks against the given `LView` based off of whether or not * Executes hooks against the given `LView` based off of whether or not
* This is the first pass. * This is the first pass.
* *
* @param lView The view instance data to run the hooks against * @param currentView The view instance data to run the hooks against
* @param firstPassHooks An array of hooks to run if we're in the first view pass * @param firstPassHooks An array of hooks to run if we're in the first view pass
* @param checkHooks An Array of hooks to run if we're not in the first view pass. * @param checkHooks An Array of hooks to run if we're not in the first view pass.
* @param checkNoChangesMode Whether or not we're in no changes mode. * @param checkNoChangesMode Whether or not we're in no changes mode.
* @param initPhaseState the current state of the init phase
* @param currentNodeIndex 3 cases depending the the value:
* - undefined: all hooks from the array should be executed (post-order case)
* - null: execute hooks only from the saved index until the end of the array (pre-order case, when
* flushing the remaining hooks)
* - number: execute hooks only from the saved index until that node index exclusive (pre-order
* case, when executing flushHooksUpTo(number))
*/ */
export function executeHooks( export function executeHooks(
currentView: LView, firstPassHooks: HookData | null, checkHooks: HookData | null, currentView: LView, firstPassHooks: HookData | null, checkHooks: HookData | null,
checkNoChangesMode: boolean, initPhase: number): void { checkNoChangesMode: boolean, initPhaseState: InitPhaseState,
currentNodeIndex: number | null | undefined): void {
if (checkNoChangesMode) return; if (checkNoChangesMode) return;
const hooksToCall = (currentView[FLAGS] & LViewFlags.InitPhaseStateMask) === initPhase ? const hooksToCall = (currentView[FLAGS] & LViewFlags.InitPhaseStateMask) === initPhaseState ?
firstPassHooks : firstPassHooks :
checkHooks; checkHooks;
if (hooksToCall) { if (hooksToCall) {
callHooks(currentView, hooksToCall, initPhase); callHooks(currentView, hooksToCall, initPhaseState, currentNodeIndex);
} }
// The init phase state must be always checked here as it may have been recursively updated // The init phase state must be always checked here as it may have been recursively updated
if ((currentView[FLAGS] & LViewFlags.InitPhaseStateMask) === initPhase && if (currentNodeIndex == null &&
initPhase !== InitPhaseState.InitPhaseCompleted) { (currentView[FLAGS] & LViewFlags.InitPhaseStateMask) === initPhaseState &&
initPhaseState !== InitPhaseState.InitPhaseCompleted) {
currentView[FLAGS] &= LViewFlags.IndexWithinInitPhaseReset; currentView[FLAGS] &= LViewFlags.IndexWithinInitPhaseReset;
currentView[FLAGS] += LViewFlags.InitPhaseStateIncrementer; currentView[FLAGS] += LViewFlags.InitPhaseStateIncrementer;
} }
@ -158,25 +206,68 @@ export function executeHooks(
* *
* @param currentView The current view * @param currentView The current view
* @param arr The array in which the hooks are found * @param arr The array in which the hooks are found
* @param initPhaseState the current state of the init phase
* @param currentNodeIndex 3 cases depending the the value:
* - undefined: all hooks from the array should be executed (post-order case)
* - null: execute hooks only from the saved index until the end of the array (pre-order case, when
* flushing the remaining hooks)
* - number: execute hooks only from the saved index until that node index exclusive (pre-order
* case, when executing flushHooksUpTo(number))
*/ */
export function callHooks(currentView: LView, arr: HookData, initPhase?: number): void { function callHooks(
let initHooksCount = 0; currentView: LView, arr: HookData, initPhase: InitPhaseState,
for (let i = 0; i < arr.length; i += 2) { currentNodeIndex: number | null | undefined): void {
const isInitHook = arr[i] < 0; const startIndex = currentNodeIndex !== undefined ?
const directiveIndex = isInitHook ? -arr[i] : arr[i] as number; (currentView[PREORDER_HOOK_FLAGS] & PreOrderHookFlags.IndexOfTheNextPreOrderHookMaskMask) :
const directive = currentView[directiveIndex]; 0;
const nodeIndexLimit = currentNodeIndex != null ? currentNodeIndex : -1;
let lastNodeIndexFound = 0;
for (let i = startIndex; i < arr.length; i++) {
const hook = arr[i + 1] as() => void; const hook = arr[i + 1] as() => void;
if (isInitHook) { if (typeof hook === 'number') {
initHooksCount++; lastNodeIndexFound = arr[i] as number;
const indexWithintInitPhase = currentView[FLAGS] >> LViewFlags.IndexWithinInitPhaseShift; if (currentNodeIndex != null && lastNodeIndexFound >= currentNodeIndex) {
// The init phase state must be always checked here as it may have been recursively updated break;
if (indexWithintInitPhase < initHooksCount &&
(currentView[FLAGS] & LViewFlags.InitPhaseStateMask) === initPhase) {
currentView[FLAGS] += LViewFlags.IndexWithinInitPhaseIncrementer;
hook.call(directive);
} }
} else { } else {
hook.call(directive); const isInitHook = arr[i] < 0;
if (isInitHook)
currentView[PREORDER_HOOK_FLAGS] += PreOrderHookFlags.NumberOfInitHooksCalledIncrementer;
if (lastNodeIndexFound < nodeIndexLimit || nodeIndexLimit == -1) {
callHook(currentView, initPhase, arr, i);
currentView[PREORDER_HOOK_FLAGS] =
(currentView[PREORDER_HOOK_FLAGS] & PreOrderHookFlags.NumberOfInitHooksCalledMask) + i +
2;
}
i++;
} }
} }
} }
/**
* Execute one hook against the current `LView`.
*
* @param currentView The current view
* @param initPhaseState the current state of the init phase
* @param arr The array in which the hooks are found
* @param i The current index within the hook data array
*/
function callHook(currentView: LView, initPhase: InitPhaseState, arr: HookData, i: number) {
const isInitHook = arr[i] < 0;
const hook = arr[i + 1] as() => void;
const directiveIndex = isInitHook ? -arr[i] : arr[i] as number;
const directive = currentView[directiveIndex];
if (isInitHook) {
const indexWithintInitPhase = currentView[FLAGS] >> LViewFlags.IndexWithinInitPhaseShift;
// The init phase state must be always checked here as it may have been recursively
// updated
if (indexWithintInitPhase <
(currentView[PREORDER_HOOK_FLAGS] >> PreOrderHookFlags.NumberOfInitHooksCalledShift) &&
(currentView[FLAGS] & LViewFlags.InitPhaseStateMask) === initPhase) {
currentView[FLAGS] += LViewFlags.IndexWithinInitPhaseIncrementer;
hook.call(directive);
}
} else {
hook.call(directive);
}
}

View File

@ -57,6 +57,8 @@ export {
elementStyleProp, elementStyleProp,
elementStylingApply, elementStylingApply,
flushHooksUpTo,
listener, listener,
store, store,
load, load,

View File

@ -24,7 +24,7 @@ import {attachPatchData, getComponentViewByInstance} from './context_discovery';
import {attachLContainerDebug, attachLViewDebug} from './debug'; import {attachLContainerDebug, attachLViewDebug} from './debug';
import {diPublicInInjector, getNodeInjectable, getOrCreateInjectable, getOrCreateNodeInjectorForNode, injectAttributeImpl} from './di'; import {diPublicInInjector, getNodeInjectable, getOrCreateInjectable, getOrCreateNodeInjectorForNode, injectAttributeImpl} from './di';
import {throwMultipleComponentError} from './errors'; import {throwMultipleComponentError} from './errors';
import {executeHooks, executeInitHooks, registerPostOrderHooks, registerPreOrderHooks} from './hooks'; import {executeHooks, executePreOrderHooks, registerPostOrderHooks, registerPreOrderHooks} from './hooks';
import {ACTIVE_INDEX, LContainer, VIEWS} from './interfaces/container'; import {ACTIVE_INDEX, LContainer, VIEWS} from './interfaces/container';
import {ComponentDef, ComponentTemplate, DirectiveDef, DirectiveDefListOrFactory, PipeDefListOrFactory, RenderFlags, ViewQueriesFunction} from './interfaces/definition'; import {ComponentDef, ComponentTemplate, DirectiveDef, DirectiveDefListOrFactory, PipeDefListOrFactory, RenderFlags, ViewQueriesFunction} from './interfaces/definition';
import {INJECTOR_BLOOM_PARENT_SIZE, NodeInjectorFactory} from './interfaces/injector'; import {INJECTOR_BLOOM_PARENT_SIZE, NodeInjectorFactory} from './interfaces/injector';
@ -48,7 +48,7 @@ import {NO_CHANGE} from './tokens';
import {attrsStylingIndexOf, setUpAttributes} from './util/attrs_utils'; import {attrsStylingIndexOf, setUpAttributes} from './util/attrs_utils';
import {INTERPOLATION_DELIMITER, renderStringify} from './util/misc_utils'; import {INTERPOLATION_DELIMITER, renderStringify} from './util/misc_utils';
import {findComponentView, getLViewParent, getRootContext, getRootView} from './util/view_traversal_utils'; import {findComponentView, getLViewParent, getRootContext, getRootView} from './util/view_traversal_utils';
import {getComponentViewByIndex, getNativeByIndex, getNativeByTNode, getTNode, isComponent, isComponentDef, isContentQueryHost, isRootView, loadInternal, readPatchedLView, unwrapRNode, viewAttachedToChangeDetector} from './util/view_utils'; import {getComponentViewByIndex, getNativeByIndex, getNativeByTNode, getTNode, isComponent, isComponentDef, isContentQueryHost, isRootView, loadInternal, readPatchedLView, resetPreOrderHookFlags, unwrapRNode, viewAttachedToChangeDetector} from './util/view_utils';
@ -84,16 +84,17 @@ export function refreshDescendantViews(lView: LView) {
if (!creationMode) { if (!creationMode) {
const checkNoChangesMode = getCheckNoChangesMode(); const checkNoChangesMode = getCheckNoChangesMode();
executeInitHooks(lView, tView, checkNoChangesMode); executePreOrderHooks(lView, tView, checkNoChangesMode, undefined);
refreshDynamicEmbeddedViews(lView); refreshDynamicEmbeddedViews(lView);
// Content query results must be refreshed before content hooks are called. // Content query results must be refreshed before content hooks are called.
refreshContentQueries(tView, lView); refreshContentQueries(tView, lView);
resetPreOrderHookFlags(lView);
executeHooks( executeHooks(
lView, tView.contentHooks, tView.contentCheckHooks, checkNoChangesMode, lView, tView.contentHooks, tView.contentCheckHooks, checkNoChangesMode,
InitPhaseState.AfterContentInitHooksToBeRun); InitPhaseState.AfterContentInitHooksToBeRun, undefined);
setHostBindings(tView, lView); setHostBindings(tView, lView);
} }
@ -180,6 +181,7 @@ export function createLView<T>(
const lView = tView.blueprint.slice() as LView; const lView = tView.blueprint.slice() as LView;
lView[HOST] = host; lView[HOST] = host;
lView[FLAGS] = flags | LViewFlags.CreationMode | LViewFlags.Attached | LViewFlags.FirstLViewPass; lView[FLAGS] = flags | LViewFlags.CreationMode | LViewFlags.Attached | LViewFlags.FirstLViewPass;
resetPreOrderHookFlags(lView);
lView[PARENT] = lView[DECLARATION_VIEW] = parentLView; lView[PARENT] = lView[DECLARATION_VIEW] = parentLView;
lView[CONTEXT] = context; lView[CONTEXT] = context;
lView[RENDERER_FACTORY] = (rendererFactory || parentLView && parentLView[RENDERER_FACTORY]) !; lView[RENDERER_FACTORY] = (rendererFactory || parentLView && parentLView[RENDERER_FACTORY]) !;
@ -405,6 +407,7 @@ export function renderEmbeddedTemplate<T>(viewToRender: LView, tView: TView, con
setPreviousOrParentTNode(null !); setPreviousOrParentTNode(null !);
oldView = enterView(viewToRender, viewToRender[T_HOST]); oldView = enterView(viewToRender, viewToRender[T_HOST]);
resetPreOrderHookFlags(viewToRender);
namespaceHTML(); namespaceHTML();
tView.template !(getRenderFlags(viewToRender), context); tView.template !(getRenderFlags(viewToRender), context);
// This must be set to false immediately after the first creation run because in an // This must be set to false immediately after the first creation run because in an
@ -459,6 +462,7 @@ function renderComponentOrTemplate<T>(
} }
// update mode pass // update mode pass
resetPreOrderHookFlags(hostView);
templateFn && templateFn(RenderFlags.Update, context); templateFn && templateFn(RenderFlags.Update, context);
refreshDescendantViews(hostView); refreshDescendantViews(hostView);
} finally { } finally {
@ -807,8 +811,8 @@ export function createTView(
firstTemplatePass: true, firstTemplatePass: true,
staticViewQueries: false, staticViewQueries: false,
staticContentQueries: false, staticContentQueries: false,
initHooks: null, preOrderHooks: null,
checkHooks: null, preOrderCheckHooks: null,
contentHooks: null, contentHooks: null,
contentCheckHooks: null, contentCheckHooks: null,
viewHooks: null, viewHooks: null,
@ -1056,6 +1060,17 @@ export function elementEnd(): void {
} }
} }
/**
* Flushes all the lifecycle hooks for directives up until (and excluding) that node index
*
* @param index The index of the element in the `LView`
*/
export function flushHooksUpTo(index: number): void {
const lView = getLView();
executePreOrderHooks(lView, lView[TVIEW], getCheckNoChangesMode(), index);
}
/** /**
* Updates the value of removes an attribute on an Element. * Updates the value of removes an attribute on an Element.
* *
@ -1748,6 +1763,10 @@ function resolveDirectives(
if (def.providersResolver) def.providersResolver(def); if (def.providersResolver) def.providersResolver(def);
} }
generateExpandoInstructionBlock(tView, tNode, directives.length); generateExpandoInstructionBlock(tView, tNode, directives.length);
const initialPreOrderHooksLength = (tView.preOrderHooks && tView.preOrderHooks.length) || 0;
const initialPreOrderCheckHooksLength =
(tView.preOrderCheckHooks && tView.preOrderCheckHooks.length) || 0;
const nodeIndex = tNode.index - HEADER_OFFSET;
for (let i = 0; i < directives.length; i++) { for (let i = 0; i < directives.length; i++) {
const def = directives[i] as DirectiveDef<any>; const def = directives[i] as DirectiveDef<any>;
@ -1758,7 +1777,9 @@ function resolveDirectives(
// Init hooks are queued now so ngOnInit is called in host components before // Init hooks are queued now so ngOnInit is called in host components before
// any projected components. // any projected components.
registerPreOrderHooks(directiveDefIdx, def, tView); registerPreOrderHooks(
directiveDefIdx, def, tView, nodeIndex, initialPreOrderHooksLength,
initialPreOrderCheckHooksLength);
} }
} }
if (exportsMap) cacheMatchingLocalNames(tNode, localRefs, exportsMap); if (exportsMap) cacheMatchingLocalNames(tNode, localRefs, exportsMap);
@ -2276,7 +2297,7 @@ export function containerRefreshStart(index: number): void {
// We need to execute init hooks here so ngOnInit hooks are called in top level views // We need to execute init hooks here so ngOnInit hooks are called in top level views
// before they are called in embedded views (for backwards compatibility). // before they are called in embedded views (for backwards compatibility).
executeInitHooks(lView, tView, getCheckNoChangesMode()); executePreOrderHooks(lView, tView, getCheckNoChangesMode(), undefined);
} }
/** /**
@ -2440,6 +2461,7 @@ export function embeddedViewEnd(): void {
refreshDescendantViews(lView); // creation mode pass refreshDescendantViews(lView); // creation mode pass
lView[FLAGS] &= ~LViewFlags.CreationMode; lView[FLAGS] &= ~LViewFlags.CreationMode;
} }
resetPreOrderHookFlags(lView);
refreshDescendantViews(lView); // update mode pass refreshDescendantViews(lView); // update mode pass
const lContainer = lView[PARENT] as LContainer; const lContainer = lView[PARENT] as LContainer;
ngDevMode && assertLContainerOrUndefined(lContainer); ngDevMode && assertLContainerOrUndefined(lContainer);
@ -2834,6 +2856,7 @@ export function checkView<T>(hostView: LView, component: T) {
const creationMode = isCreationMode(hostView); const creationMode = isCreationMode(hostView);
try { try {
resetPreOrderHookFlags(hostView);
namespaceHTML(); namespaceHTML();
creationMode && executeViewQueryFn(RenderFlags.Create, hostTView, component); creationMode && executeViewQueryFn(RenderFlags.Create, hostTView, component);
templateFn(getRenderFlags(hostView), component); templateFn(getRenderFlags(hostView), component);

View File

@ -45,8 +45,9 @@ export const CHILD_HEAD = 14;
export const CHILD_TAIL = 15; export const CHILD_TAIL = 15;
export const CONTENT_QUERIES = 16; export const CONTENT_QUERIES = 16;
export const DECLARATION_VIEW = 17; export const DECLARATION_VIEW = 17;
export const PREORDER_HOOK_FLAGS = 18;
/** Size of LView's header. Necessary to adjust for it when setting slots. */ /** Size of LView's header. Necessary to adjust for it when setting slots. */
export const HEADER_OFFSET = 19; export const HEADER_OFFSET = 20;
// This interface replaces the real LView interface if it is an arg or a // This interface replaces the real LView interface if it is an arg or a
@ -215,6 +216,11 @@ export interface LView extends Array<any> {
* context. * context.
*/ */
[DECLARATION_VIEW]: LView|null; [DECLARATION_VIEW]: LView|null;
/**
* More flags for this view. See PreOrderHookFlags for more info.
*/
[PREORDER_HOOK_FLAGS]: PreOrderHookFlags;
} }
/** Flags associated with an LView (saved in LView[FLAGS]) */ /** Flags associated with an LView (saved in LView[FLAGS]) */
@ -296,6 +302,20 @@ export const enum InitPhaseState {
InitPhaseCompleted = 0b11, InitPhaseCompleted = 0b11,
} }
/** More flags associated with an LView (saved in LView[FLAGS_MORE]) */
export const enum PreOrderHookFlags {
/** The index of the next pre-order hook to be called in the hooks array, on the first 16
bits */
IndexOfTheNextPreOrderHookMaskMask = 0b01111111111111111,
/**
* The number of init hooks that have already been called, on the last 16 bits
*/
NumberOfInitHooksCalledIncrementer = 0b010000000000000000,
NumberOfInitHooksCalledShift = 16,
NumberOfInitHooksCalledMask = 0b11111111111111110000000000000000,
}
/** /**
* Set of instructions used to process host bindings efficiently. * Set of instructions used to process host bindings efficiently.
* *
@ -438,21 +458,21 @@ export interface TView {
pipeRegistry: PipeDefList|null; pipeRegistry: PipeDefList|null;
/** /**
* Array of ngOnInit and ngDoCheck hooks that should be executed for this view in * Array of ngOnInit, ngOnChanges and ngDoCheck hooks that should be executed for this view in
* creation mode. * creation mode.
* *
* Even indices: Directive index * Even indices: Directive index
* Odd indices: Hook function * Odd indices: Hook function
*/ */
initHooks: HookData|null; preOrderHooks: HookData|null;
/** /**
* Array of ngDoCheck hooks that should be executed for this view in update mode. * Array of ngOnChanges and ngDoCheck hooks that should be executed for this view in update mode.
* *
* Even indices: Directive index * Even indices: Directive index
* Odd indices: Hook function * Odd indices: Hook function
*/ */
checkHooks: HookData|null; preOrderCheckHooks: HookData|null;
/** /**
* Array of ngAfterContentInit and ngAfterContentChecked hooks that should be executed * Array of ngAfterContentInit and ngAfterContentChecked hooks that should be executed
@ -591,8 +611,14 @@ export interface RootContext {
/** /**
* Array of hooks that should be executed for a view and their directive indices. * Array of hooks that should be executed for a view and their directive indices.
* *
* Even indices: Directive index * For each node of the view, the following data is stored:
* Odd indices: Hook function * 1) Node index (optional)
* 2) A series of number/function pairs where:
* - even indices are directive indices
* - odd indices are hook functions
*
* Special cases:
* - a negative directive index flags an init hook (ngOnInit, ngAfterContentInit, ngAfterViewInit)
*/ */
export type HookData = (number | (() => void))[]; export type HookData = (number | (() => void))[];

View File

@ -99,6 +99,7 @@ export const angularCoreEnv: {[name: string]: Function} = {
'ɵelementStylingMap': r3.elementStylingMap, 'ɵelementStylingMap': r3.elementStylingMap,
'ɵelementStyleProp': r3.elementStyleProp, 'ɵelementStyleProp': r3.elementStyleProp,
'ɵelementStylingApply': r3.elementStylingApply, 'ɵelementStylingApply': r3.elementStylingApply,
'ɵflushHooksUpTo': r3.flushHooksUpTo,
'ɵtemplate': r3.template, 'ɵtemplate': r3.template,
'ɵtext': r3.text, 'ɵtext': r3.text,
'ɵtextBinding': r3.textBinding, 'ɵtextBinding': r3.textBinding,

View File

@ -13,6 +13,7 @@ import {executeHooks} from './hooks';
import {ComponentDef, DirectiveDef} from './interfaces/definition'; import {ComponentDef, DirectiveDef} from './interfaces/definition';
import {TElementNode, TNode, TViewNode} from './interfaces/node'; import {TElementNode, TNode, TViewNode} from './interfaces/node';
import {BINDING_INDEX, CONTEXT, DECLARATION_VIEW, FLAGS, InitPhaseState, LView, LViewFlags, OpaqueViewState, TVIEW} from './interfaces/view'; import {BINDING_INDEX, CONTEXT, DECLARATION_VIEW, FLAGS, InitPhaseState, LView, LViewFlags, OpaqueViewState, TVIEW} from './interfaces/view';
import {resetPreOrderHookFlags} from './util/view_utils';
@ -304,9 +305,10 @@ export function leaveView(newView: LView): void {
lView[FLAGS] &= ~LViewFlags.CreationMode; lView[FLAGS] &= ~LViewFlags.CreationMode;
} else { } else {
try { try {
resetPreOrderHookFlags(lView);
executeHooks( executeHooks(
lView, tView.viewHooks, tView.viewCheckHooks, checkNoChangesMode, lView, tView.viewHooks, tView.viewCheckHooks, checkNoChangesMode,
InitPhaseState.AfterViewInitHooksToBeRun); InitPhaseState.AfterViewInitHooksToBeRun, undefined);
} finally { } finally {
// Views are clean and in update mode after being checked, so these bits are cleared // Views are clean and in update mode after being checked, so these bits are cleared
lView[FLAGS] &= ~(LViewFlags.Dirty | LViewFlags.FirstLViewPass); lView[FLAGS] &= ~(LViewFlags.Dirty | LViewFlags.FirstLViewPass);

View File

@ -13,7 +13,7 @@ import {ComponentDef, DirectiveDef} from '../interfaces/definition';
import {TNode, TNodeFlags} from '../interfaces/node'; import {TNode, TNodeFlags} from '../interfaces/node';
import {RNode} from '../interfaces/renderer'; import {RNode} from '../interfaces/renderer';
import {StylingContext} from '../interfaces/styling'; import {StylingContext} from '../interfaces/styling';
import {FLAGS, HEADER_OFFSET, HOST, LView, LViewFlags, PARENT, TData, TVIEW} from '../interfaces/view'; import {FLAGS, HEADER_OFFSET, HOST, LView, LViewFlags, PARENT, PREORDER_HOOK_FLAGS, TData, TVIEW} from '../interfaces/view';
@ -197,3 +197,11 @@ export function viewAttachedToChangeDetector(view: LView): boolean {
export function viewAttachedToContainer(view: LView): boolean { export function viewAttachedToContainer(view: LView): boolean {
return isLContainer(view[PARENT]); return isLContainer(view[PARENT]);
} }
/**
* Resets the pre-order hook flags of the view.
* @param lView the LView on which the flags are reset
*/
export function resetPreOrderHookFlags(lView: LView) {
lView[PREORDER_HOOK_FLAGS] = 0;
}

View File

@ -6,14 +6,18 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {Component, Directive, Input, Type} from '@angular/core'; import {Component, Directive, DoCheck, Input, OnChanges, OnInit, SimpleChanges, Type} from '@angular/core';
import {TestBed} from '@angular/core/testing'; import {TestBed} from '@angular/core/testing';
import {onlyInIvy} from '@angular/private/testing'; import {modifiedInIvy, onlyInIvy} from '@angular/private/testing';
describe('exports', () => { describe('exports', () => {
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule( TestBed.configureTestingModule({
{declarations: [AppComp, ComponentToReference, DirToReference, DirWithCompInput]}); declarations: [
AppComp, ComponentToReference, DirToReference, DirToReferenceWithPreOrderHooks,
DirWithCompInput
]
});
}); });
it('should support export of DOM element', () => { it('should support export of DOM element', () => {
@ -36,6 +40,68 @@ describe('exports', () => {
expect(fixture.nativeElement.innerHTML).toEqual('<div dir=""></div> Drew'); expect(fixture.nativeElement.innerHTML).toEqual('<div dir=""></div> Drew');
}); });
describe('input changes in hooks', () => {
it('should support forward reference', () => {
const fixture = initWithTemplate(
AppComp, '<div dirOnChange #myDir="dirOnChange" [in]="true"></div> {{ myDir.name }}');
fixture.detectChanges();
expect(fixture.nativeElement.innerHTML)
.toEqual('<div dironchange="" ng-reflect-in="true" title="Drew!?@"></div> Drew!?@');
});
modifiedInIvy('Supporting input changes in hooks is limited in Ivy')
.it('should support backward reference', () => {
const fixture = initWithTemplate(
AppComp, '{{ myDir.name }} <div dirOnChange #myDir="dirOnChange" [in]="true"></div>');
fixture.detectChanges();
expect(fixture.nativeElement.innerHTML)
.toEqual('Drew!?@ <div dironchange="" ng-reflect-in="true" title="Drew!?@"></div>');
});
onlyInIvy('Supporting input changes in hooks is limited in Ivy')
.it('should not support backward reference', () => {
expect(() => {
const fixture = initWithTemplate(
AppComp,
'{{ myDir.name }} <div dirOnChange #myDir="dirOnChange" [in]="true"></div>');
fixture.detectChanges();
})
.toThrowError(
/ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked/);
});
modifiedInIvy('Supporting input changes in hooks is limited in Ivy')
.it('should support reference on the same node', () => {
const fixture = initWithTemplate(
AppComp,
'<div dirOnChange #myDir="dirOnChange" [in]="true" [id]="myDir.name"></div>');
fixture.detectChanges();
expect(fixture.nativeElement.innerHTML)
.toEqual(
'<div dironchange="" ng-reflect-in="true" id="Drew!?@" title="Drew!?@"></div>');
});
onlyInIvy('Supporting input changes in hooks is limited in Ivy')
.it('should not support reference on the same node', () => {
expect(() => {
const fixture = initWithTemplate(
AppComp,
'<div dirOnChange #myDir="dirOnChange" [in]="true" [id]="myDir.name"></div>');
fixture.detectChanges();
})
.toThrowError(
/ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked/);
});
it('should support input referenced by host binding on that directive', () => {
const fixture =
initWithTemplate(AppComp, '<div dirOnChange #myDir="dirOnChange" [in]="true"></div>');
fixture.detectChanges();
expect(fixture.nativeElement.innerHTML)
.toEqual('<div dironchange="" ng-reflect-in="true" title="Drew!?@"></div>');
});
});
onlyInIvy('Different error message is thrown in View Engine') onlyInIvy('Different error message is thrown in View Engine')
.it('should throw if export name is not found', () => { .it('should throw if export name is not found', () => {
expect(() => { expect(() => {
@ -95,3 +161,12 @@ class DirToReference {
class DirWithCompInput { class DirWithCompInput {
@Input('dirWithInput') comp: ComponentToReference|null = null; @Input('dirWithInput') comp: ComponentToReference|null = null;
} }
@Directive({selector: '[dirOnChange]', exportAs: 'dirOnChange', host: {'[title]': 'name'}})
class DirToReferenceWithPreOrderHooks implements OnInit, OnChanges, DoCheck {
@Input() in : any = null;
name = 'Drew';
ngOnChanges(changes: SimpleChanges) { this.name += '!'; }
ngOnInit() { this.name += '?'; }
ngDoCheck() { this.name += '@'; }
}

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {Component, Input, OnChanges, SimpleChanges} from '@angular/core'; import {Component, Directive, Input, OnChanges, SimpleChanges} from '@angular/core';
import {TestBed} from '@angular/core/testing'; import {TestBed} from '@angular/core/testing';
describe('ngOnChanges', () => { describe('ngOnChanges', () => {
@ -56,4 +56,116 @@ describe('ngOnChanges', () => {
fixture.detectChanges(); fixture.detectChanges();
expect(log).toEqual(['c: 0 -> 3']); expect(log).toEqual(['c: 0 -> 3']);
}); });
}); });
it('should call all hooks in correct order when several directives on same node', () => {
let log: string[] = [];
class AllHooks {
id: number = -1;
/** @internal */
private _log(hook: string, id: number) { log.push(hook + id); }
ngOnChanges() { this._log('onChanges', this.id); }
ngOnInit() { this._log('onInit', this.id); }
ngDoCheck() { this._log('doCheck', this.id); }
ngAfterContentInit() { this._log('afterContentInit', this.id); }
ngAfterContentChecked() { this._log('afterContentChecked', this.id); }
ngAfterViewInit() { this._log('afterViewInit', this.id); }
ngAfterViewChecked() { this._log('afterViewChecked', this.id); }
}
@Directive({selector: 'div'})
class DirA extends AllHooks {
@Input('a') id: number = 0;
}
@Directive({selector: 'div'})
class DirB extends AllHooks {
@Input('b') id: number = 0;
}
@Directive({selector: 'div'})
class DirC extends AllHooks {
@Input('c') id: number = 0;
}
@Component({selector: 'app-comp', template: '<div [a]="1" [b]="2" [c]="3"></div>'})
class AppComp {
}
TestBed.configureTestingModule({declarations: [AppComp, DirA, DirB, DirC]});
const fixture = TestBed.createComponent(AppComp);
fixture.detectChanges();
expect(log).toEqual([
'onChanges1',
'onInit1',
'doCheck1',
'onChanges2',
'onInit2',
'doCheck2',
'onChanges3',
'onInit3',
'doCheck3',
'afterContentInit1',
'afterContentChecked1',
'afterContentInit2',
'afterContentChecked2',
'afterContentInit3',
'afterContentChecked3',
'afterViewInit1',
'afterViewChecked1',
'afterViewInit2',
'afterViewChecked2',
'afterViewInit3',
'afterViewChecked3'
]);
});
it('should call hooks after setting directives inputs', () => {
let log: string[] = [];
@Directive({selector: 'div'})
class DirA {
@Input() a: number = 0;
ngOnInit() { log.push('onInitA' + this.a); }
}
@Directive({selector: 'div'})
class DirB {
@Input() b: number = 0;
ngOnInit() { log.push('onInitB' + this.b); }
ngDoCheck() { log.push('doCheckB' + this.b); }
}
@Directive({selector: 'div'})
class DirC {
@Input() c: number = 0;
ngOnInit() { log.push('onInitC' + this.c); }
ngDoCheck() { log.push('doCheckC' + this.c); }
}
@Component({
selector: 'app-comp',
template: '<div [a]="id" [b]="id" [c]="id"></div><div [a]="id" [b]="id" [c]="id"></div>'
})
class AppComp {
id = 0;
}
TestBed.configureTestingModule({declarations: [AppComp, DirA, DirB, DirC]});
const fixture = TestBed.createComponent(AppComp);
fixture.detectChanges();
expect(log).toEqual([
'onInitA0', 'onInitB0', 'doCheckB0', 'onInitC0', 'doCheckC0', 'onInitA0', 'onInitB0',
'doCheckB0', 'onInitC0', 'doCheckC0'
]);
log = [];
fixture.componentInstance.id = 1;
fixture.detectChanges();
expect(log).toEqual(['doCheckB1', 'doCheckC1', 'doCheckB1', 'doCheckC1']);
});

View File

@ -107,6 +107,9 @@
{ {
"name": "PARENT_INJECTOR" "name": "PARENT_INJECTOR"
}, },
{
"name": "PREORDER_HOOK_FLAGS"
},
{ {
"name": "QUERIES" "name": "QUERIES"
}, },
@ -194,6 +197,9 @@
{ {
"name": "cacheMatchingLocalNames" "name": "cacheMatchingLocalNames"
}, },
{
"name": "callHook"
},
{ {
"name": "callHooks" "name": "callHooks"
}, },
@ -282,7 +288,7 @@
"name": "executeHooks" "name": "executeHooks"
}, },
{ {
"name": "executeInitHooks" "name": "executePreOrderHooks"
}, },
{ {
"name": "executeViewQueryFn" "name": "executeViewQueryFn"
@ -599,6 +605,9 @@
{ {
"name": "resetComponentState" "name": "resetComponentState"
}, },
{
"name": "resetPreOrderHookFlags"
},
{ {
"name": "resolveDirectives" "name": "resolveDirectives"
}, },

View File

@ -92,6 +92,9 @@
{ {
"name": "PARENT_INJECTOR" "name": "PARENT_INJECTOR"
}, },
{
"name": "PREORDER_HOOK_FLAGS"
},
{ {
"name": "RENDERER" "name": "RENDERER"
}, },
@ -149,6 +152,9 @@
{ {
"name": "bloomAdd" "name": "bloomAdd"
}, },
{
"name": "callHook"
},
{ {
"name": "callHooks" "name": "callHooks"
}, },
@ -207,7 +213,7 @@
"name": "executeHooks" "name": "executeHooks"
}, },
{ {
"name": "executeInitHooks" "name": "executePreOrderHooks"
}, },
{ {
"name": "executeViewQueryFn" "name": "executeViewQueryFn"
@ -428,6 +434,9 @@
{ {
"name": "resetComponentState" "name": "resetComponentState"
}, },
{
"name": "resetPreOrderHookFlags"
},
{ {
"name": "setBindingRoot" "name": "setBindingRoot"
}, },

View File

@ -179,6 +179,9 @@
{ {
"name": "PARENT_INJECTOR" "name": "PARENT_INJECTOR"
}, },
{
"name": "PREORDER_HOOK_FLAGS"
},
{ {
"name": "QUERIES" "name": "QUERIES"
}, },
@ -431,6 +434,9 @@
{ {
"name": "cacheMatchingLocalNames" "name": "cacheMatchingLocalNames"
}, },
{
"name": "callHook"
},
{ {
"name": "callHooks" "name": "callHooks"
}, },
@ -584,15 +590,15 @@
{ {
"name": "executeHooks" "name": "executeHooks"
}, },
{
"name": "executeInitHooks"
},
{ {
"name": "executeNodeAction" "name": "executeNodeAction"
}, },
{ {
"name": "executeOnDestroys" "name": "executeOnDestroys"
}, },
{
"name": "executePreOrderHooks"
},
{ {
"name": "executeViewQueryFn" "name": "executeViewQueryFn"
}, },
@ -620,6 +626,9 @@
{ {
"name": "findViaComponent" "name": "findViaComponent"
}, },
{
"name": "flushHooksUpTo"
},
{ {
"name": "forwardRef" "name": "forwardRef"
}, },
@ -1157,6 +1166,9 @@
{ {
"name": "resetComponentState" "name": "resetComponentState"
}, },
{
"name": "resetPreOrderHookFlags"
},
{ {
"name": "resolveDirectives" "name": "resolveDirectives"
}, },

View File

@ -9,7 +9,7 @@
import {ComponentFactoryResolver, OnDestroy, SimpleChange, SimpleChanges, ViewContainerRef} from '../../src/core'; import {ComponentFactoryResolver, OnDestroy, SimpleChange, SimpleChanges, ViewContainerRef} from '../../src/core';
import {AttributeMarker, ComponentTemplate, LifecycleHooksFeature, NO_CHANGE, NgOnChangesFeature, defineComponent, defineDirective, injectComponentFactoryResolver} from '../../src/render3/index'; import {AttributeMarker, ComponentTemplate, LifecycleHooksFeature, NO_CHANGE, NgOnChangesFeature, defineComponent, defineDirective, injectComponentFactoryResolver} from '../../src/render3/index';
import {bind, container, containerRefreshEnd, containerRefreshStart, directiveInject, element, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, listener, markDirty, projection, projectionDef, store, template, text} from '../../src/render3/instructions'; import {bind, container, containerRefreshEnd, containerRefreshStart, directiveInject, element, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, flushHooksUpTo, listener, markDirty, projection, projectionDef, store, template, text} from '../../src/render3/instructions';
import {RenderFlags} from '../../src/render3/interfaces/definition'; import {RenderFlags} from '../../src/render3/interfaces/definition';
import {NgIf} from './common_with_def'; import {NgIf} from './common_with_def';
@ -139,6 +139,7 @@ describe('lifecycles', () => {
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
elementProperty(0, 'val', 1); elementProperty(0, 'val', 1);
flushHooksUpTo(1);
elementProperty(1, 'val', 2); elementProperty(1, 'val', 2);
} }
}, 2, 0, directives); }, 2, 0, directives);
@ -289,8 +290,11 @@ describe('lifecycles', () => {
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
elementProperty(0, 'val', 1); elementProperty(0, 'val', 1);
flushHooksUpTo(1);
elementProperty(1, 'val', 1); elementProperty(1, 'val', 1);
flushHooksUpTo(2);
elementProperty(2, 'val', 2); elementProperty(2, 'val', 2);
flushHooksUpTo(3);
elementProperty(3, 'val', 2); elementProperty(3, 'val', 2);
} }
}, 4, 0, directives); }, 4, 0, directives);
@ -345,6 +349,7 @@ describe('lifecycles', () => {
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
elementProperty(0, 'val', 1); elementProperty(0, 'val', 1);
flushHooksUpTo(2);
elementProperty(2, 'val', 5); elementProperty(2, 'val', 5);
containerRefreshStart(1); containerRefreshStart(1);
{ {
@ -385,6 +390,7 @@ describe('lifecycles', () => {
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
elementProperty(0, 'val', 1); elementProperty(0, 'val', 1);
flushHooksUpTo(2);
elementProperty(2, 'val', 5); elementProperty(2, 'val', 5);
containerRefreshStart(1); containerRefreshStart(1);
{ {
@ -623,6 +629,7 @@ describe('lifecycles', () => {
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
elementProperty(0, 'val', 1); elementProperty(0, 'val', 1);
flushHooksUpTo(3);
elementProperty(3, 'val', 4); elementProperty(3, 'val', 4);
containerRefreshStart(2); containerRefreshStart(2);
{ {
@ -746,6 +753,7 @@ describe('lifecycles', () => {
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
elementProperty(0, 'val', 1); elementProperty(0, 'val', 1);
flushHooksUpTo(2);
elementProperty(2, 'val', 2); elementProperty(2, 'val', 2);
} }
}, 4, 0, directives); }, 4, 0, directives);
@ -814,8 +822,11 @@ describe('lifecycles', () => {
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
elementProperty(0, 'val', 1); elementProperty(0, 'val', 1);
flushHooksUpTo(1);
elementProperty(1, 'val', 1); elementProperty(1, 'val', 1);
flushHooksUpTo(3);
elementProperty(3, 'val', 2); elementProperty(3, 'val', 2);
flushHooksUpTo(4);
elementProperty(4, 'val', 2); elementProperty(4, 'val', 2);
} }
}, 6, 0, directives); }, 6, 0, directives);
@ -844,6 +855,7 @@ describe('lifecycles', () => {
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
elementProperty(0, 'val', 1); elementProperty(0, 'val', 1);
flushHooksUpTo(3);
elementProperty(3, 'val', 4); elementProperty(3, 'val', 4);
containerRefreshStart(2); containerRefreshStart(2);
{ {
@ -1091,6 +1103,7 @@ describe('lifecycles', () => {
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
elementProperty(0, 'val', 1); elementProperty(0, 'val', 1);
flushHooksUpTo(1);
elementProperty(1, 'val', 2); elementProperty(1, 'val', 2);
} }
}, 2, 0, defs); }, 2, 0, defs);
@ -1138,8 +1151,11 @@ describe('lifecycles', () => {
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
elementProperty(0, 'val', 1); elementProperty(0, 'val', 1);
flushHooksUpTo(1);
elementProperty(1, 'val', 1); elementProperty(1, 'val', 1);
flushHooksUpTo(2);
elementProperty(2, 'val', 2); elementProperty(2, 'val', 2);
flushHooksUpTo(3);
elementProperty(3, 'val', 2); elementProperty(3, 'val', 2);
} }
}, 4, 0, defs); }, 4, 0, defs);
@ -1162,6 +1178,7 @@ describe('lifecycles', () => {
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
elementProperty(0, 'val', bind(ctx.val)); elementProperty(0, 'val', bind(ctx.val));
flushHooksUpTo(1);
elementProperty(1, 'val', bind(ctx.val)); elementProperty(1, 'val', bind(ctx.val));
} }
}, 2, 2, [Comp, ProjectedComp]); }, 2, 2, [Comp, ProjectedComp]);
@ -1177,6 +1194,7 @@ describe('lifecycles', () => {
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
elementProperty(0, 'val', 1); elementProperty(0, 'val', 1);
flushHooksUpTo(1);
elementProperty(1, 'val', 2); elementProperty(1, 'val', 2);
} }
}, 2, 0, [ParentComp]); }, 2, 0, [ParentComp]);
@ -1201,6 +1219,7 @@ describe('lifecycles', () => {
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
elementProperty(0, 'val', 1); elementProperty(0, 'val', 1);
flushHooksUpTo(2);
elementProperty(2, 'val', 4); elementProperty(2, 'val', 4);
containerRefreshStart(1); containerRefreshStart(1);
{ {
@ -1240,6 +1259,7 @@ describe('lifecycles', () => {
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
elementProperty(0, 'val', 1); elementProperty(0, 'val', 1);
flushHooksUpTo(2);
elementProperty(2, 'val', 4); elementProperty(2, 'val', 4);
containerRefreshStart(1); containerRefreshStart(1);
{ {
@ -1325,6 +1345,7 @@ describe('lifecycles', () => {
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
elementProperty(0, 'val', 1); elementProperty(0, 'val', 1);
flushHooksUpTo(2);
elementProperty(2, 'val', 4); elementProperty(2, 'val', 4);
containerRefreshStart(1); containerRefreshStart(1);
{ {
@ -1486,6 +1507,7 @@ describe('lifecycles', () => {
} }
if (rf1 & RenderFlags.Update) { if (rf1 & RenderFlags.Update) {
elementProperty(0, 'val', bind('1')); elementProperty(0, 'val', bind('1'));
flushHooksUpTo(1);
elementProperty(1, 'val', bind('2')); elementProperty(1, 'val', bind('2'));
} }
embeddedViewEnd(); embeddedViewEnd();
@ -1602,8 +1624,11 @@ describe('lifecycles', () => {
} }
if (rf1 & RenderFlags.Update) { if (rf1 & RenderFlags.Update) {
elementProperty(0, 'val', 1); elementProperty(0, 'val', 1);
flushHooksUpTo(1);
elementProperty(1, 'val', 1); elementProperty(1, 'val', 1);
flushHooksUpTo(2);
elementProperty(2, 'val', 2); elementProperty(2, 'val', 2);
flushHooksUpTo(3);
elementProperty(3, 'val', 2); elementProperty(3, 'val', 2);
} }
embeddedViewEnd(); embeddedViewEnd();
@ -1648,6 +1673,7 @@ describe('lifecycles', () => {
} }
if (rf1 & RenderFlags.Update) { if (rf1 & RenderFlags.Update) {
elementProperty(0, 'val', bind('1')); elementProperty(0, 'val', bind('1'));
flushHooksUpTo(2);
elementProperty(2, 'val', bind('3')); elementProperty(2, 'val', bind('3'));
containerRefreshStart(1); containerRefreshStart(1);
{ {
@ -1741,6 +1767,7 @@ describe('lifecycles', () => {
} }
if (rf1 & RenderFlags.Update) { if (rf1 & RenderFlags.Update) {
elementProperty(0, 'val', bind('1')); elementProperty(0, 'val', bind('1'));
flushHooksUpTo(2);
elementProperty(2, 'val', bind('5')); elementProperty(2, 'val', bind('5'));
containerRefreshStart(1); containerRefreshStart(1);
{ {
@ -2134,6 +2161,7 @@ describe('lifecycles', () => {
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
elementProperty(0, 'val1', bind(1)); elementProperty(0, 'val1', bind(1));
elementProperty(0, 'publicVal2', bind(1)); elementProperty(0, 'publicVal2', bind(1));
flushHooksUpTo(1);
elementProperty(1, 'val1', bind(2)); elementProperty(1, 'val1', bind(2));
elementProperty(1, 'publicVal2', bind(2)); elementProperty(1, 'publicVal2', bind(2));
} }
@ -2271,6 +2299,7 @@ describe('lifecycles', () => {
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
elementProperty(0, 'val1', bind(1)); elementProperty(0, 'val1', bind(1));
elementProperty(0, 'publicVal2', bind(1)); elementProperty(0, 'publicVal2', bind(1));
flushHooksUpTo(1);
elementProperty(1, 'val1', bind(2)); elementProperty(1, 'val1', bind(2));
elementProperty(1, 'publicVal2', bind(2)); elementProperty(1, 'publicVal2', bind(2));
} }
@ -2318,10 +2347,13 @@ describe('lifecycles', () => {
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
elementProperty(0, 'val1', bind(1)); elementProperty(0, 'val1', bind(1));
elementProperty(0, 'publicVal2', bind(1)); elementProperty(0, 'publicVal2', bind(1));
flushHooksUpTo(1);
elementProperty(1, 'val1', bind(2)); elementProperty(1, 'val1', bind(2));
elementProperty(1, 'publicVal2', bind(2)); elementProperty(1, 'publicVal2', bind(2));
flushHooksUpTo(2);
elementProperty(2, 'val1', bind(3)); elementProperty(2, 'val1', bind(3));
elementProperty(2, 'publicVal2', bind(3)); elementProperty(2, 'publicVal2', bind(3));
flushHooksUpTo(3);
elementProperty(3, 'val1', bind(4)); elementProperty(3, 'val1', bind(4));
elementProperty(3, 'publicVal2', bind(4)); elementProperty(3, 'publicVal2', bind(4));
} }
@ -2452,6 +2484,7 @@ describe('lifecycles', () => {
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
elementProperty(0, 'val1', bind(1)); elementProperty(0, 'val1', bind(1));
elementProperty(0, 'publicVal2', bind(1)); elementProperty(0, 'publicVal2', bind(1));
flushHooksUpTo(2);
elementProperty(2, 'val1', bind(5)); elementProperty(2, 'val1', bind(5));
elementProperty(2, 'publicVal2', bind(5)); elementProperty(2, 'publicVal2', bind(5));
containerRefreshStart(1); containerRefreshStart(1);
@ -2538,6 +2571,7 @@ describe('lifecycles', () => {
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
elementProperty(0, 'val1', bind(1)); elementProperty(0, 'val1', bind(1));
elementProperty(0, 'publicVal2', bind(1)); elementProperty(0, 'publicVal2', bind(1));
flushHooksUpTo(2);
elementProperty(2, 'val1', bind(5)); elementProperty(2, 'val1', bind(5));
elementProperty(2, 'publicVal2', bind(5)); elementProperty(2, 'publicVal2', bind(5));
containerRefreshStart(1); containerRefreshStart(1);
@ -2757,6 +2791,7 @@ describe('lifecycles', () => {
// even though the *value* itself never changed. // even though the *value* itself never changed.
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
elementProperty(0, 'val', 1); elementProperty(0, 'val', 1);
flushHooksUpTo(1);
elementProperty(1, 'val', 2); elementProperty(1, 'val', 2);
} }
}, 2, 0, [Comp]); }, 2, 0, [Comp]);
@ -2800,6 +2835,7 @@ describe('lifecycles', () => {
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
elementProperty(0, 'val', 1); elementProperty(0, 'val', 1);
flushHooksUpTo(1);
elementProperty(1, 'val', 2); elementProperty(1, 'val', 2);
} }
}, 2, 0, [Parent]); }, 2, 0, [Parent]);
@ -2843,6 +2879,7 @@ describe('lifecycles', () => {
element(1, 'view'); element(1, 'view');
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
flushHooksUpTo(1);
elementProperty(1, 'val', bind(ctx.val)); elementProperty(1, 'val', bind(ctx.val));
} }
}, 2, 1, [View]); }, 2, 1, [View]);
@ -2866,8 +2903,11 @@ describe('lifecycles', () => {
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
elementProperty(0, 'val', bind(1)); elementProperty(0, 'val', bind(1));
flushHooksUpTo(1);
elementProperty(1, 'val', bind(1)); elementProperty(1, 'val', bind(1));
flushHooksUpTo(2);
elementProperty(2, 'val', bind(2)); elementProperty(2, 'val', bind(2));
flushHooksUpTo(3);
elementProperty(3, 'val', bind(2)); elementProperty(3, 'val', bind(2));
} }
}, 4, 4, [Parent, Content]); }, 4, 4, [Parent, Content]);

View File

@ -6,43 +6,41 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {fixmeIvy} from '@angular/private/testing';
import {ElementArrayFinder, ElementFinder, browser, by, element} from 'protractor'; import {ElementArrayFinder, ElementFinder, browser, by, element} from 'protractor';
import {verifyNoBrowserErrors} from '../../../../test-utils'; import {verifyNoBrowserErrors} from '../../../../test-utils';
fixmeIvy('FW-1051: Directives are updated after the execution of the template function') describe('simpleNgModel example', () => {
.describe('simpleNgModel example', () => { afterEach(verifyNoBrowserErrors);
afterEach(verifyNoBrowserErrors); let input: ElementFinder;
let input: ElementFinder; let paragraphs: ElementArrayFinder;
let paragraphs: ElementArrayFinder; let button: ElementFinder;
let button: ElementFinder;
beforeEach(() => { beforeEach(() => {
browser.get('/simpleNgModel'); browser.get('/simpleNgModel');
input = element(by.css('input')); input = element(by.css('input'));
paragraphs = element.all(by.css('p')); paragraphs = element.all(by.css('p'));
button = element(by.css('button')); button = element(by.css('button'));
}); });
it('should update the domain model as you type', () => { it('should update the domain model as you type', () => {
input.click(); input.click();
input.sendKeys('Carson'); input.sendKeys('Carson');
expect(paragraphs.get(0).getText()).toEqual('Value: Carson'); expect(paragraphs.get(0).getText()).toEqual('Value: Carson');
}); });
it('should report the validity correctly', () => { it('should report the validity correctly', () => {
expect(paragraphs.get(1).getText()).toEqual('Valid: false'); expect(paragraphs.get(1).getText()).toEqual('Valid: false');
input.click(); input.click();
input.sendKeys('a'); input.sendKeys('a');
expect(paragraphs.get(1).getText()).toEqual('Valid: true'); expect(paragraphs.get(1).getText()).toEqual('Valid: true');
}); });
it('should set the value by changing the domain model', () => { it('should set the value by changing the domain model', () => {
button.click(); button.click();
expect(input.getAttribute('value')).toEqual('Nancy'); expect(input.getAttribute('value')).toEqual('Nancy');
}); });
}); });