diff --git a/integration/_payload-limits.json b/integration/_payload-limits.json index be104d8e49..61f989f000 100644 --- a/integration/_payload-limits.json +++ b/integration/_payload-limits.json @@ -12,7 +12,7 @@ "master": { "uncompressed": { "runtime": 1440, - "main": 13659, + "main": 13921, "polyfills": 38390 } } diff --git a/packages/compiler-cli/test/compliance/r3_compiler_compliance_spec.ts b/packages/compiler-cli/test/compliance/r3_compiler_compliance_spec.ts index 757b034542..d256953c43 100644 --- a/packages/compiler-cli/test/compliance/r3_compiler_compliance_spec.ts +++ b/packages/compiler-cli/test/compliance/r3_compiler_compliance_spec.ts @@ -798,6 +798,7 @@ describe('compiler compliance', () => { if (rf & 2) { const $myComp$ = $r3$.ɵnextContext(); const $foo$ = $r3$.ɵreference(1); + $r3$.ɵflushHooksUpTo(1); $r3$.ɵtextBinding(1, $r3$.ɵinterpolation2("", $myComp$.salutation, " ", $foo$, "")); } } @@ -1255,6 +1256,7 @@ describe('compiler compliance', () => { } if (rf & 2) { $r3$.ɵelementProperty(0, "ngIf", $r3$.ɵbind(ctx.visible)); + $r3$.ɵflushHooksUpTo(1); $r3$.ɵelementProperty(1, "ngIf", $r3$.ɵbind(ctx.visible)); } } @@ -1947,6 +1949,7 @@ describe('compiler compliance', () => { } if (rf & 2) { $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)), "")); } }, @@ -2061,6 +2064,7 @@ describe('compiler compliance', () => { } if (rf & 2) { const $user$ = $r3$.ɵreference(1); + $r3$.ɵflushHooksUpTo(2); $r3$.ɵtextBinding(2, $r3$.ɵinterpolation1("Hello ", $user$.value, "!")); } }, @@ -2123,6 +2127,7 @@ describe('compiler compliance', () => { $r3$.ɵnextContext(); const $foo$ = $r3$.ɵreference(1); const $baz$ = $r3$.ɵreference(5); + $r3$.ɵflushHooksUpTo(1); $r3$.ɵtextBinding(1, $r3$.ɵinterpolation3("", $foo$, "-", $bar$, "-", $baz$, "")); } } @@ -2138,6 +2143,7 @@ describe('compiler compliance', () => { const $bar$ = $r3$.ɵreference(4); $r3$.ɵnextContext(); const $foo$ = $r3$.ɵreference(1); + $r3$.ɵflushHooksUpTo(1); $r3$.ɵtextBinding(1, $r3$.ɵinterpolation2(" ", $foo$, "-", $bar$, " ")); } } @@ -2157,6 +2163,7 @@ describe('compiler compliance', () => { } if (rf & 2) { const $foo$ = $r3$.ɵreference(1); + $r3$.ɵflushHooksUpTo(2); $r3$.ɵtextBinding(2, $r3$.ɵinterpolation1(" ", $foo$, " ")); } }, @@ -2209,6 +2216,7 @@ describe('compiler compliance', () => { if (rf & 2) { const $item$ = $i0$.ɵnextContext().$implicit; const $foo$ = $i0$.ɵreference(2); + $r3$.ɵflushHooksUpTo(1); $i0$.ɵtextBinding(1, $i0$.ɵinterpolation2(" ", $foo$, " - ", $item$, " ")); } } @@ -2222,6 +2230,7 @@ describe('compiler compliance', () => { } if (rf & 2) { const $app$ = $i0$.ɵnextContext(); + $r3$.ɵflushHooksUpTo(3); $i0$.ɵelementProperty(3, "ngIf", $i0$.ɵbind($app$.showing)); } } @@ -2313,6 +2322,7 @@ describe('compiler compliance', () => { } if (rf & 2) { $r3$.ɵelementProperty(0, "name", $r3$.ɵbind(ctx.name1)); + $r3$.ɵflushHooksUpTo(1); $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$.ɵ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]; }, encapsulation: 2 @@ -2505,6 +2518,7 @@ describe('compiler compliance', () => { } if (rf & 2) { const $item$ = ctx.$implicit; + $r3$.ɵflushHooksUpTo(1); $r3$.ɵtextBinding(1, $r3$.ɵinterpolation1("", $item$.name, "")); } } @@ -2522,6 +2536,7 @@ describe('compiler compliance', () => { $r3$.ɵelementEnd(); } if (rf & 2) { + $r3$.ɵflushHooksUpTo(1); $r3$.ɵelementProperty(1, "forOf", $r3$.ɵbind(ctx.items)); } }, @@ -2586,6 +2601,7 @@ describe('compiler compliance', () => { if (rf & 2) { const $info$ = ctx.$implicit; const $item$ = $r3$.ɵnextContext().$implicit; + $r3$.ɵflushHooksUpTo(1); $r3$.ɵtextBinding(1, $r3$.ɵinterpolation2(" ", $item$.name, ": ", $info$.description, " ")); } } @@ -2603,7 +2619,9 @@ describe('compiler compliance', () => { } if (rf & 2) { const $item$ = ctx.$implicit; + $r3$.ɵflushHooksUpTo(2); $r3$.ɵtextBinding(2, $r3$.ɵinterpolation1("", IDENT.name, "")); + $r3$.ɵflushHooksUpTo(4); $r3$.ɵelementProperty(4, "forOf", $r3$.ɵbind(IDENT.infos)); } } @@ -2622,6 +2640,7 @@ describe('compiler compliance', () => { $r3$.ɵelementEnd(); } if (rf & 2) { + $r3$.ɵflushHooksUpTo(1); $r3$.ɵelementProperty(1, "forOf", $r3$.ɵbind(ctx.items)); } }, diff --git a/packages/compiler-cli/test/compliance/r3_view_compiler_binding_spec.ts b/packages/compiler-cli/test/compliance/r3_view_compiler_binding_spec.ts index bc4c880ec8..7b922505cb 100644 --- a/packages/compiler-cli/test/compliance/r3_view_compiler_binding_spec.ts +++ b/packages/compiler-cli/test/compliance/r3_view_compiler_binding_spec.ts @@ -44,6 +44,7 @@ describe('compiler compliance: bindings', () => { $i0$.ɵelementEnd(); } if (rf & 2) { + $r3$.ɵflushHooksUpTo(1); $i0$.ɵtextBinding(1, $i0$.ɵinterpolation1("Hello ", $ctx$.name, "")); } }`; @@ -473,6 +474,7 @@ describe('compiler compliance: bindings', () => { } if (rf & 2) { const $_r0$ = $i0$.ɵreference(1); + $r3$.ɵflushHooksUpTo(4); $i0$.ɵtextBinding(4, $i0$.ɵinterpolation1(" ", $_r0$.id, " ")); } } diff --git a/packages/compiler-cli/test/compliance/r3_view_compiler_i18n_spec.ts b/packages/compiler-cli/test/compliance/r3_view_compiler_i18n_spec.ts index 1186e84357..6216bcc15b 100644 --- a/packages/compiler-cli/test/compliance/r3_view_compiler_i18n_spec.ts +++ b/packages/compiler-cli/test/compliance/r3_view_compiler_i18n_spec.ts @@ -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(ctx.valueB)); $r3$.ɵi18nApply(2); + $r3$.ɵflushHooksUpTo(3); $r3$.ɵi18nExp($r3$.ɵbind(ctx.valueA)); $r3$.ɵi18nExp($r3$.ɵbind(ctx.valueB)); $r3$.ɵi18nExp($r3$.ɵbind((ctx.valueA + ctx.valueB))); @@ -436,6 +437,7 @@ describe('i18n support in the view compiler', () => { } if (rf & 2) { const $outer_r1$ = ctx.$implicit; + $r3$.ɵflushHooksUpTo(1); $r3$.ɵi18nExp($r3$.ɵbind($r3$.ɵpipeBind1(2, 0, $outer_r1$))); $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(ctx.valueB)); $r3$.ɵi18nApply(2); + $r3$.ɵflushHooksUpTo(3); $r3$.ɵi18nExp($r3$.ɵbind(ctx.valueA)); $r3$.ɵi18nExp($r3$.ɵbind(ctx.valueB)); $r3$.ɵi18nExp($r3$.ɵbind((ctx.valueA + ctx.valueB))); @@ -565,6 +568,7 @@ describe('i18n support in the view compiler', () => { } if (rf & 2) { const $outer_r1$ = ctx.$implicit; + $r3$.ɵflushHooksUpTo(1); $r3$.ɵi18nExp($r3$.ɵbind($r3$.ɵpipeBind1(2, 0, $outer_r1$))); $r3$.ɵi18nApply(3); } @@ -730,6 +734,7 @@ describe('i18n support in the view compiler', () => { $r3$.ɵelementEnd(); } if (rf & 2) { + $r3$.ɵflushHooksUpTo(1); $r3$.ɵi18nExp($r3$.ɵbind(ctx.valueA)); $r3$.ɵi18nApply(1); } @@ -756,6 +761,7 @@ describe('i18n support in the view compiler', () => { $r3$.ɵelementEnd(); } if (rf & 2) { + $r3$.ɵflushHooksUpTo(1); $r3$.ɵi18nExp($r3$.ɵbind(ctx.valueA)); $r3$.ɵi18nApply(1); } @@ -786,6 +792,7 @@ describe('i18n support in the view compiler', () => { $r3$.ɵelementEnd(); } if (rf & 2) { + $r3$.ɵflushHooksUpTo(1); $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$.ɵi18nApply(1); @@ -829,10 +836,13 @@ describe('i18n support in the view compiler', () => { $r3$.ɵelementEnd(); } if (rf & 2) { + $r3$.ɵflushHooksUpTo(1); $r3$.ɵi18nExp($r3$.ɵbind(ctx.one)); $r3$.ɵi18nApply(1); + $r3$.ɵflushHooksUpTo(3); $r3$.ɵi18nExp($r3$.ɵbind($r3$.ɵpipeBind1(4, 3, ctx.two))); $r3$.ɵi18nApply(3); + $r3$.ɵflushHooksUpTo(6); $r3$.ɵi18nExp($r3$.ɵbind(((ctx.three + ctx.four) + ctx.five))); $r3$.ɵi18nApply(6); } @@ -897,8 +907,10 @@ describe('i18n support in the view compiler', () => { $r3$.ɵelementEnd(); } if (rf & 2) { + $r3$.ɵflushHooksUpTo(1); $r3$.ɵi18nExp($r3$.ɵbind(ctx.one)); $r3$.ɵi18nApply(1); + $r3$.ɵflushHooksUpTo(4); $r3$.ɵi18nExp($r3$.ɵbind($r3$.ɵpipeBind1(5, 3, ctx.two))); $r3$.ɵi18nExp($r3$.ɵbind(ctx.nestedInBlockTwo)); $r3$.ɵi18nApply(4); @@ -965,11 +977,13 @@ describe('i18n support in the view compiler', () => { $r3$.ɵelementEnd(); } if (rf & 2) { + $r3$.ɵflushHooksUpTo(2); $r3$.ɵi18nExp($r3$.ɵbind(ctx.valueB)); $r3$.ɵi18nExp($r3$.ɵbind(ctx.valueC)); $r3$.ɵi18nApply(3); $r3$.ɵi18nExp($r3$.ɵbind(ctx.valueA)); $r3$.ɵi18nApply(1); + $r3$.ɵflushHooksUpTo(7); $r3$.ɵi18nExp($r3$.ɵbind(ctx.valueE)); $r3$.ɵi18nApply(8); $r3$.ɵi18nExp($r3$.ɵbind($r3$.ɵpipeBind1(6, 5, ctx.valueD))); @@ -1018,6 +1032,7 @@ describe('i18n support in the view compiler', () => { } if (rf & 2) { const $ctx_r0$ = $r3$.ɵnextContext(); + $r3$.ɵflushHooksUpTo(2); $r3$.ɵi18nExp($r3$.ɵbind($ctx_r0$.valueA)); $r3$.ɵi18nExp($r3$.ɵbind($r3$.ɵpipeBind1(4, 2, $ctx_r0$.valueB))); $r3$.ɵi18nApply(2); @@ -1034,6 +1049,7 @@ describe('i18n support in the view compiler', () => { $r3$.ɵelementEnd(); } if (rf & 2) { + $r3$.ɵflushHooksUpTo(2); $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$); } if (rf & 2) { + $r3$.ɵflushHooksUpTo(1); $r3$.ɵelementProperty(1, "ngIf", $r3$.ɵbind(ctx.visible)); + $r3$.ɵflushHooksUpTo(2); $r3$.ɵelementProperty(2, "ngIf", $r3$.ɵbind(ctx.visible)); } } @@ -1147,6 +1165,7 @@ describe('i18n support in the view compiler', () => { } if (rf & 2) { const $ctx_r0$ = $r3$.ɵnextContext(); + $r3$.ɵflushHooksUpTo(4); $r3$.ɵelementProperty(4, "ngIf", $r3$.ɵbind($ctx_r0$.exists)); $r3$.ɵi18nExp($r3$.ɵbind($ctx_r0$.valueA)); $r3$.ɵi18nExp($r3$.ɵbind($r3$.ɵpipeBind1(3, 3, $ctx_r0$.valueB))); @@ -1196,7 +1215,9 @@ describe('i18n support in the view compiler', () => { $r3$.ɵelementEnd(); } if (rf & 2) { + $r3$.ɵflushHooksUpTo(2); $r3$.ɵelementProperty(2, "ngIf", $r3$.ɵbind(ctx.visible)); + $r3$.ɵflushHooksUpTo(3); $r3$.ɵelementProperty(3, "ngIf", $r3$.ɵbind(!ctx.visible)); } } @@ -1228,6 +1249,7 @@ describe('i18n support in the view compiler', () => { } if (rf & 2) { const $ctx_r0$ = $r3$.ɵnextContext(); + $r3$.ɵflushHooksUpTo(1); $r3$.ɵi18nExp($r3$.ɵbind($ctx_r0$.valueA)); $r3$.ɵi18nApply(1); } @@ -1290,6 +1312,7 @@ describe('i18n support in the view compiler', () => { $r3$.ɵelementEnd(); } if (rf & 2) { + $r3$.ɵflushHooksUpTo(1); $r3$.ɵi18nExp($r3$.ɵbind(ctx.age)); $r3$.ɵi18nApply(1); } @@ -1378,6 +1401,7 @@ describe('i18n support in the view compiler', () => { $r3$.ɵelementContainerEnd(); } if (rf & 2) { + $r3$.ɵflushHooksUpTo(1); $r3$.ɵi18nExp($r3$.ɵbind($r3$.ɵpipeBind1(2, 1, ctx.valueA))); $r3$.ɵi18nApply(1); } @@ -1462,6 +1486,7 @@ describe('i18n support in the view compiler', () => { $r3$.ɵelementEnd(); } if (rf & 2) { + $r3$.ɵflushHooksUpTo(1); $r3$.ɵi18nExp($r3$.ɵbind($r3$.ɵpipeBind1(4, 1, ctx.valueB))); $r3$.ɵi18nApply(1); } @@ -1507,6 +1532,7 @@ describe('i18n support in the view compiler', () => { $r3$.ɵelementContainerEnd(); } if (rf & 2) { + $r3$.ɵflushHooksUpTo(2); $r3$.ɵi18nExp($r3$.ɵbind(ctx.age)); $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"); } if (rf & 2) { + $r3$.ɵflushHooksUpTo(1); $r3$.ɵi18nExp($r3$.ɵbind(ctx.gender)); $r3$.ɵi18nApply(1); } @@ -1771,6 +1798,7 @@ describe('i18n support in the view compiler', () => { $r3$.ɵelementEnd(); } if (rf & 2) { + $r3$.ɵflushHooksUpTo(1); $r3$.ɵi18nExp($r3$.ɵbind(ctx.gender)); $r3$.ɵi18nApply(1); } @@ -1850,6 +1878,7 @@ describe('i18n support in the view compiler', () => { } if (rf & 2) { const $ctx_r0$ = $r3$.ɵnextContext(); + $r3$.ɵflushHooksUpTo(1); $r3$.ɵi18nExp($r3$.ɵbind($ctx_r0$.age)); $r3$.ɵi18nApply(1); } @@ -1871,6 +1900,7 @@ describe('i18n support in the view compiler', () => { } if (rf & 2) { const $ctx_r1$ = $r3$.ɵnextContext(); + $r3$.ɵflushHooksUpTo(2); $r3$.ɵi18nExp($r3$.ɵbind($ctx_r1$.count)); $r3$.ɵi18nExp($r3$.ɵbind($ctx_r1$.count)); $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$); } if (rf & 2) { + $r3$.ɵflushHooksUpTo(1); $r3$.ɵi18nExp($r3$.ɵbind(ctx.gender)); $r3$.ɵi18nApply(1); + $r3$.ɵflushHooksUpTo(2); $r3$.ɵelementProperty(2, "ngIf", $r3$.ɵbind(ctx.visible)); + $r3$.ɵflushHooksUpTo(3); $r3$.ɵelementProperty(3, "ngIf", $r3$.ɵbind(ctx.available)); } } @@ -1917,6 +1950,7 @@ describe('i18n support in the view compiler', () => { $r3$.ɵelementEnd(); } if (rf & 2) { + $r3$.ɵflushHooksUpTo(1); $r3$.ɵi18nExp($r3$.ɵbind(ctx.age)); $r3$.ɵi18nExp($r3$.ɵbind(ctx.other)); $r3$.ɵi18nApply(1); @@ -2004,6 +2038,7 @@ describe('i18n support in the view compiler', () => { $r3$.ɵelementEnd(); } if (rf & 2) { + $r3$.ɵflushHooksUpTo(1); $r3$.ɵi18nExp($r3$.ɵbind(ctx.gender)); $r3$.ɵi18nExp($r3$.ɵbind(((ctx.ageA + ctx.ageB) + ctx.ageC))); $r3$.ɵi18nApply(1); @@ -2045,6 +2080,7 @@ describe('i18n support in the view compiler', () => { $r3$.ɵelementEnd(); } if (rf & 2) { + $r3$.ɵflushHooksUpTo(1); $r3$.ɵi18nExp($r3$.ɵbind(ctx.gender)); $r3$.ɵi18nExp($r3$.ɵbind(ctx.age)); $r3$.ɵi18nApply(1); @@ -2116,6 +2152,7 @@ describe('i18n support in the view compiler', () => { $r3$.ɵelementEnd(); } if (rf & 2) { + $r3$.ɵflushHooksUpTo(3); $r3$.ɵelementProperty(3, "ngIf", $r3$.ɵbind(ctx.visible)); $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(); } if (rf & 2) { + $r3$.ɵflushHooksUpTo(1); $r3$.ɵi18nExp($r3$.ɵbind(ctx.age)); $r3$.ɵi18nExp($r3$.ɵbind(ctx.gender)); $r3$.ɵi18nApply(1); @@ -2220,6 +2258,7 @@ describe('i18n support in the view compiler', () => { $r3$.ɵelementEnd(); } if (rf & 2) { + $r3$.ɵflushHooksUpTo(2); $r3$.ɵelementProperty(2, "ngIf", $r3$.ɵbind(ctx.ageVisible)); $r3$.ɵi18nExp($r3$.ɵbind(ctx.gender)); $r3$.ɵi18nApply(1); @@ -2286,6 +2325,7 @@ describe('i18n support in the view compiler', () => { $r3$.ɵelementEnd(); } if (rf & 2) { + $r3$.ɵflushHooksUpTo(2); $r3$.ɵelementProperty(2, "ngIf", $r3$.ɵbind(ctx.ageVisible)); $r3$.ɵi18nExp($r3$.ɵbind(ctx.gender)); $r3$.ɵi18nExp($r3$.ɵbind(ctx.weight)); @@ -2328,6 +2368,7 @@ describe('i18n support in the view compiler', () => { $r3$.ɵelementEnd(); } if (rf & 2) { + $r3$.ɵflushHooksUpTo(1); $r3$.ɵi18nExp($r3$.ɵbind(ctx.gender)); $r3$.ɵi18nExp($r3$.ɵbind(ctx.weight)); $r3$.ɵi18nExp($r3$.ɵbind(ctx.height)); diff --git a/packages/compiler-cli/test/compliance/r3_view_compiler_styling_spec.ts b/packages/compiler-cli/test/compliance/r3_view_compiler_styling_spec.ts index 007bd762a8..a98686358f 100644 --- a/packages/compiler-cli/test/compliance/r3_view_compiler_styling_spec.ts +++ b/packages/compiler-cli/test/compliance/r3_view_compiler_styling_spec.ts @@ -227,7 +227,9 @@ describe('compiler compliance: styling', () => { } if (rf & 2) { $r3$.ɵelementProperty(0, "@foo", $r3$.ɵbind(ctx.exp)); + $r3$.ɵflushHooksUpTo(1); $r3$.ɵelementProperty(1, "@bar", $r3$.ɵbind(undefined)); + $r3$.ɵflushHooksUpTo(2); $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$.ɵelementClassProp(0, 0, $r3$.ɵpipeBind2(4, 10, $ctx$.fooExp, 2000)); $r3$.ɵelementStylingApply(0); + $r3$.ɵflushHooksUpTo(5); $r3$.ɵtextBinding(5, $r3$.ɵinterpolation1(" ", $ctx$.item, "")); } } diff --git a/packages/compiler-cli/test/compliance/r3_view_compiler_template_spec.ts b/packages/compiler-cli/test/compliance/r3_view_compiler_template_spec.ts index b0ed81d0c7..8f56fbe23a 100644 --- a/packages/compiler-cli/test/compliance/r3_view_compiler_template_spec.ts +++ b/packages/compiler-cli/test/compliance/r3_view_compiler_template_spec.ts @@ -75,6 +75,7 @@ describe('compiler compliance: template', () => { const $outer1$ = $i0$.ɵnextContext().$implicit; const $myComp1$ = $i0$.ɵnextContext(); $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), " ")); } } @@ -87,6 +88,7 @@ describe('compiler compliance: template', () => { } if (rf & 2) { const $myComp2$ = $i0$.ɵnextContext(2); + $r3$.ɵflushHooksUpTo(1); $i0$.ɵelementProperty(1, "ngForOf", $i0$.ɵbind($myComp2$.items)); } } @@ -99,6 +101,7 @@ describe('compiler compliance: template', () => { } if (rf & 2) { const $outer2$ = ctx.$implicit; + $r3$.ɵflushHooksUpTo(1); $i0$.ɵelementProperty(1, "ngForOf", $i0$.ɵbind($outer2$.items)); } } @@ -207,6 +210,7 @@ describe('compiler compliance: template', () => { if (rf & 2) { const $item$ = ctx.$implicit; const $i$ = ctx.index; + $r3$.ɵflushHooksUpTo(1); $i0$.ɵtextBinding(1, $i0$.ɵinterpolation2(" ", $i$, " - ", $item$, " ")); } } @@ -262,6 +266,7 @@ describe('compiler compliance: template', () => { const $div$ = $i0$.ɵnextContext(); const $i$ = $div$.index; const $item$ = $div$.$implicit; + $r3$.ɵflushHooksUpTo(1); $i0$.ɵtextBinding(1, $i0$.ɵinterpolation2(" ", $i$, " - ", $item$, " ")); } } @@ -274,6 +279,7 @@ describe('compiler compliance: template', () => { } if (rf & 2) { const $app$ = $i0$.ɵnextContext(); + $r3$.ɵflushHooksUpTo(1); $i0$.ɵelementProperty(1, "ngIf", $i0$.ɵbind($app$.showing)); } } @@ -330,6 +336,7 @@ describe('compiler compliance: template', () => { if (rf & 2) { const $middle$ = $i0$.ɵnextContext().$implicit; const $myComp$ = $i0$.ɵnextContext(2); + $r3$.ɵflushHooksUpTo(1); $i0$.ɵtextBinding(1, $i0$.ɵinterpolation2(" ", $middle$.value, " - ", $myComp$.name, " ")); } } @@ -342,6 +349,7 @@ describe('compiler compliance: template', () => { } if (rf & 2) { const $middle$ = ctx.$implicit; + $r3$.ɵflushHooksUpTo(1); $i0$.ɵelementProperty(1, "ngForOf", $i0$.ɵbind($middle$.items)); } } @@ -354,6 +362,7 @@ describe('compiler compliance: template', () => { } if (rf & 2) { const $outer$ = ctx.$implicit; + $r3$.ɵflushHooksUpTo(1); $i0$.ɵelementProperty(1, "ngForOf", $i0$.ɵbind($outer$.items)); } } diff --git a/packages/compiler/src/render3/r3_identifiers.ts b/packages/compiler/src/render3/r3_identifiers.ts index 4ffac6f60c..29c13c0d67 100644 --- a/packages/compiler/src/render3/r3_identifiers.ts +++ b/packages/compiler/src/render3/r3_identifiers.ts @@ -31,6 +31,8 @@ export class Identifiers { static elementProperty: o.ExternalReference = {name: 'ɵelementProperty', moduleName: CORE}; + static flushHooksUpTo: o.ExternalReference = {name: 'ɵflushHooksUpTo', moduleName: CORE}; + static componentHostSyntheticProperty: o.ExternalReference = {name: 'ɵcomponentHostSyntheticProperty', moduleName: CORE}; diff --git a/packages/compiler/src/render3/view/template.ts b/packages/compiler/src/render3/view/template.ts index 836484a384..751d89eda4 100644 --- a/packages/compiler/src/render3/view/template.ts +++ b/packages/compiler/src/render3/view/template.ts @@ -120,6 +120,11 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver * all local refs and context variables are available for matching. */ 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. */ private _tempVariables: o.Statement[] = []; /** @@ -451,10 +456,10 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver if (bindings.size) { bindings.forEach(binding => { this.updateInstruction( - span, R3.i18nExp, + index, span, R3.i18nExp, () => [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) { this.creationInstruction(span, R3.i18nEnd); @@ -639,7 +644,7 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver converted.expressions.forEach(expression => { hasBindings = true; 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, LocalResolver const args = this.constantPool.getConstLiteral(o.literalArr(i18nAttrArgs), true); this.creationInstruction(element.sourceSpan, R3.i18nAttributes, [index, args]); 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, LocalResolver const hasValue = value instanceof LiteralPrimitive ? !!value.value : true; this.allocateBindingSlots(value); const bindingName = prepareSyntheticPropertyName(input.name); - this.updateInstruction(input.sourceSpan, R3.elementProperty, () => { + this.updateInstruction(elementIndex, input.sourceSpan, R3.elementProperty, () => { return [ o.literal(elementIndex), o.literal(bindingName), (hasValue ? this.convertPropertyBinding(implicit, value) : emptyValueBindInstruction) @@ -737,7 +742,7 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver } } this.allocateBindingSlots(value); - this.updateInstruction(input.sourceSpan, instruction, () => { + this.updateInstruction(elementIndex, input.sourceSpan, instruction, () => { return [ o.literal(elementIndex), o.literal(attrName), this.convertPropertyBinding(implicit, value), ...params @@ -839,7 +844,7 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver template.inputs.forEach(input => { const value = input.value.visit(this._valueConverter); this.allocateBindingSlots(value); - this.updateInstruction(template.sourceSpan, R3.elementProperty, () => { + this.updateInstruction(templateIndex, template.sourceSpan, R3.elementProperty, () => { return [ o.literal(templateIndex), o.literal(input.name), this.convertPropertyBinding(context, value) @@ -880,7 +885,7 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver const value = text.value.visit(this._valueConverter); this.allocateBindingSlots(value); this.updateInstruction( - text.sourceSpan, R3.textBinding, + nodeIndex, text.sourceSpan, R3.textBinding, () => [o.literal(nodeIndex), this.convertPropertyBinding(o.variable(CONTEXT_NAME), value)]); } @@ -966,7 +971,7 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver if (createMode) { this.creationInstruction(instruction.sourceSpan, instruction.reference, paramsFn); } 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, LocalResolver } private updateInstruction( - span: ParseSourceSpan|null, reference: o.ExternalReference, + nodeIndex: number, span: ParseSourceSpan|null, reference: o.ExternalReference, 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 || []); } diff --git a/packages/core/src/core_render3_private_export.ts b/packages/core/src/core_render3_private_export.ts index 70c1696bc4..2c69fe477a 100644 --- a/packages/core/src/core_render3_private_export.ts +++ b/packages/core/src/core_render3_private_export.ts @@ -104,6 +104,7 @@ export { elementStyleProp as ɵelementStyleProp, elementStylingApply as ɵelementStylingApply, elementClassProp as ɵelementClassProp, + flushHooksUpTo as ɵflushHooksUpTo, textBinding as ɵtextBinding, template as ɵtemplate, embeddedViewEnd as ɵembeddedViewEnd, diff --git a/packages/core/src/render3/component.ts b/packages/core/src/render3/component.ts index 566b57d76e..77c33778d5 100644 --- a/packages/core/src/render3/component.ts +++ b/packages/core/src/render3/component.ts @@ -29,7 +29,7 @@ import {renderInitialClasses, renderInitialStyles} from './styling/class_and_sty import {publishDefaultGlobalUtils} from './util/global_utils'; import {defaultScheduler, renderStringify} from './util/misc_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( refreshDescendantViews(rootView); // creation mode pass rootView[FLAGS] &= ~LViewFlags.CreationMode; + resetPreOrderHookFlags(rootView); refreshDescendantViews(rootView); // update mode pass } finally { leaveView(oldView); @@ -248,7 +249,7 @@ export function LifecycleHooksFeature(component: any, def: ComponentDef): v const rootTView = readPatchedLView(component) ![TVIEW]; 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 // LNode). registerPostOrderHooks( diff --git a/packages/core/src/render3/hooks.ts b/packages/core/src/render3/hooks.ts index f901adf7de..af60ffeb9e 100644 --- a/packages/core/src/render3/hooks.ts +++ b/packages/core/src/render3/hooks.ts @@ -10,7 +10,7 @@ import {assertEqual} from '../util/assert'; import {DirectiveDef} from './interfaces/definition'; 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. * - * The TView's hooks arrays are arranged in alternating pairs of directiveIndex and hookFunction, - * i.e.: `[directiveIndexA, hookFunctionA, directiveIndexB, hookFunctionB, ...]`. For `OnChanges` - * hooks, the `directiveIndex` will be *negative*, signaling {@link callHooks} that the - * `hookFunction` must be passed the the appropriate {@link SimpleChanges} object. + * Sets up the pre-order hooks on the provided `tView`, + * see {@link HookData} for details about the data structure. * * @param directiveIndex The index of the directive in LView * @param directiveDef The definition containing the hooks to setup in 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( - directiveIndex: number, directiveDef: DirectiveDef, tView: TView): void { + directiveIndex: number, directiveDef: DirectiveDef, tView: TView, nodeIndex: number, + initialPreOrderHooksLength: number, initialPreOrderCheckHooksLength: number): void { ngDevMode && assertEqual(tView.firstTemplatePass, true, 'Should only be called on first template pass'); 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) { - (tView.initHooks || (tView.initHooks = [])).push(directiveIndex, onChanges); - (tView.checkHooks || (tView.checkHooks = [])).push(directiveIndex, onChanges); + (tView.preOrderHooks || (tView.preOrderHooks = [])).push(directiveIndex, onChanges); + (tView.preOrderCheckHooks || (tView.preOrderCheckHooks = [])).push(directiveIndex, onChanges); } if (onInit) { - (tView.initHooks || (tView.initHooks = [])).push(-directiveIndex, onInit); + (tView.preOrderHooks || (tView.preOrderHooks = [])).push(-directiveIndex, onInit); } if (doCheck) { - (tView.initHooks || (tView.initHooks = [])).push(directiveIndex, doCheck); - (tView.checkHooks || (tView.checkHooks = [])).push(directiveIndex, doCheck); + (tView.preOrderHooks || (tView.preOrderHooks = [])).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 * components and directives must be called *before* their hosts. * - * Sets up the content, view, and destroy hooks on the provided `tView` such that - * they're added in alternating pairs of directiveIndex and hookFunction, - * i.e.: `[directiveIndexA, hookFunctionA, directiveIndexB, hookFunctionB, ...]` + * Sets up the content, view, and destroy hooks on the provided `tView`, + * see {@link HookData} for details about the data structure. * * NOTE: This does not set up `onChanges`, `onInit` or `doCheck`, those are set up * 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 hooks that are to be run during the initialization of a directive such * 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 tView Static data for the view containing the hooks to be executed * @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( - currentView: LView, tView: TView, checkNoChangesMode: boolean): void { +export function executePreOrderHooks( + currentView: LView, tView: TView, checkNoChangesMode: boolean, + currentNodeIndex: number | undefined): void { if (!checkNoChangesMode) { executeHooks( - currentView, tView.initHooks, tView.checkHooks, checkNoChangesMode, - InitPhaseState.OnInitHooksToBeRun); + currentView, tView.preOrderHooks, tView.preOrderCheckHooks, checkNoChangesMode, + 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 * 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 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 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( 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; - const hooksToCall = (currentView[FLAGS] & LViewFlags.InitPhaseStateMask) === initPhase ? + const hooksToCall = (currentView[FLAGS] & LViewFlags.InitPhaseStateMask) === initPhaseState ? firstPassHooks : checkHooks; 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 - if ((currentView[FLAGS] & LViewFlags.InitPhaseStateMask) === initPhase && - initPhase !== InitPhaseState.InitPhaseCompleted) { + if (currentNodeIndex == null && + (currentView[FLAGS] & LViewFlags.InitPhaseStateMask) === initPhaseState && + initPhaseState !== InitPhaseState.InitPhaseCompleted) { currentView[FLAGS] &= LViewFlags.IndexWithinInitPhaseReset; currentView[FLAGS] += LViewFlags.InitPhaseStateIncrementer; } @@ -158,25 +206,68 @@ export function executeHooks( * * @param currentView The current view * @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 { - let initHooksCount = 0; - for (let i = 0; i < arr.length; i += 2) { - const isInitHook = arr[i] < 0; - const directiveIndex = isInitHook ? -arr[i] : arr[i] as number; - const directive = currentView[directiveIndex]; +function callHooks( + currentView: LView, arr: HookData, initPhase: InitPhaseState, + currentNodeIndex: number | null | undefined): void { + const startIndex = currentNodeIndex !== undefined ? + (currentView[PREORDER_HOOK_FLAGS] & PreOrderHookFlags.IndexOfTheNextPreOrderHookMaskMask) : + 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; - if (isInitHook) { - initHooksCount++; - const indexWithintInitPhase = currentView[FLAGS] >> LViewFlags.IndexWithinInitPhaseShift; - // The init phase state must be always checked here as it may have been recursively updated - if (indexWithintInitPhase < initHooksCount && - (currentView[FLAGS] & LViewFlags.InitPhaseStateMask) === initPhase) { - currentView[FLAGS] += LViewFlags.IndexWithinInitPhaseIncrementer; - hook.call(directive); + if (typeof hook === 'number') { + lastNodeIndexFound = arr[i] as number; + if (currentNodeIndex != null && lastNodeIndexFound >= currentNodeIndex) { + break; } } 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); + } +} diff --git a/packages/core/src/render3/index.ts b/packages/core/src/render3/index.ts index d6a2faa716..1e7c4966c9 100644 --- a/packages/core/src/render3/index.ts +++ b/packages/core/src/render3/index.ts @@ -57,6 +57,8 @@ export { elementStyleProp, elementStylingApply, + flushHooksUpTo, + listener, store, load, diff --git a/packages/core/src/render3/instructions.ts b/packages/core/src/render3/instructions.ts index a09a3d4d58..d40746a4e4 100644 --- a/packages/core/src/render3/instructions.ts +++ b/packages/core/src/render3/instructions.ts @@ -24,7 +24,7 @@ import {attachPatchData, getComponentViewByInstance} from './context_discovery'; import {attachLContainerDebug, attachLViewDebug} from './debug'; import {diPublicInInjector, getNodeInjectable, getOrCreateInjectable, getOrCreateNodeInjectorForNode, injectAttributeImpl} from './di'; 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 {ComponentDef, ComponentTemplate, DirectiveDef, DirectiveDefListOrFactory, PipeDefListOrFactory, RenderFlags, ViewQueriesFunction} from './interfaces/definition'; 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 {INTERPOLATION_DELIMITER, renderStringify} from './util/misc_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) { const checkNoChangesMode = getCheckNoChangesMode(); - executeInitHooks(lView, tView, checkNoChangesMode); + executePreOrderHooks(lView, tView, checkNoChangesMode, undefined); refreshDynamicEmbeddedViews(lView); // Content query results must be refreshed before content hooks are called. refreshContentQueries(tView, lView); + resetPreOrderHookFlags(lView); executeHooks( lView, tView.contentHooks, tView.contentCheckHooks, checkNoChangesMode, - InitPhaseState.AfterContentInitHooksToBeRun); + InitPhaseState.AfterContentInitHooksToBeRun, undefined); setHostBindings(tView, lView); } @@ -180,6 +181,7 @@ export function createLView( const lView = tView.blueprint.slice() as LView; lView[HOST] = host; lView[FLAGS] = flags | LViewFlags.CreationMode | LViewFlags.Attached | LViewFlags.FirstLViewPass; + resetPreOrderHookFlags(lView); lView[PARENT] = lView[DECLARATION_VIEW] = parentLView; lView[CONTEXT] = context; lView[RENDERER_FACTORY] = (rendererFactory || parentLView && parentLView[RENDERER_FACTORY]) !; @@ -405,6 +407,7 @@ export function renderEmbeddedTemplate(viewToRender: LView, tView: TView, con setPreviousOrParentTNode(null !); oldView = enterView(viewToRender, viewToRender[T_HOST]); + resetPreOrderHookFlags(viewToRender); namespaceHTML(); tView.template !(getRenderFlags(viewToRender), context); // This must be set to false immediately after the first creation run because in an @@ -459,6 +462,7 @@ function renderComponentOrTemplate( } // update mode pass + resetPreOrderHookFlags(hostView); templateFn && templateFn(RenderFlags.Update, context); refreshDescendantViews(hostView); } finally { @@ -807,8 +811,8 @@ export function createTView( firstTemplatePass: true, staticViewQueries: false, staticContentQueries: false, - initHooks: null, - checkHooks: null, + preOrderHooks: null, + preOrderCheckHooks: null, contentHooks: null, contentCheckHooks: 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. * @@ -1748,6 +1763,10 @@ function resolveDirectives( if (def.providersResolver) def.providersResolver(def); } 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++) { const def = directives[i] as DirectiveDef; @@ -1758,7 +1777,9 @@ function resolveDirectives( // Init hooks are queued now so ngOnInit is called in host components before // any projected components. - registerPreOrderHooks(directiveDefIdx, def, tView); + registerPreOrderHooks( + directiveDefIdx, def, tView, nodeIndex, initialPreOrderHooksLength, + initialPreOrderCheckHooksLength); } } 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 // 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 lView[FLAGS] &= ~LViewFlags.CreationMode; } + resetPreOrderHookFlags(lView); refreshDescendantViews(lView); // update mode pass const lContainer = lView[PARENT] as LContainer; ngDevMode && assertLContainerOrUndefined(lContainer); @@ -2834,6 +2856,7 @@ export function checkView(hostView: LView, component: T) { const creationMode = isCreationMode(hostView); try { + resetPreOrderHookFlags(hostView); namespaceHTML(); creationMode && executeViewQueryFn(RenderFlags.Create, hostTView, component); templateFn(getRenderFlags(hostView), component); diff --git a/packages/core/src/render3/interfaces/view.ts b/packages/core/src/render3/interfaces/view.ts index c60313b735..1ca2b06b35 100644 --- a/packages/core/src/render3/interfaces/view.ts +++ b/packages/core/src/render3/interfaces/view.ts @@ -45,8 +45,9 @@ export const CHILD_HEAD = 14; export const CHILD_TAIL = 15; export const CONTENT_QUERIES = 16; export const DECLARATION_VIEW = 17; +export const PREORDER_HOOK_FLAGS = 18; /** 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 @@ -215,6 +216,11 @@ export interface LView extends Array { * context. */ [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]) */ @@ -296,6 +302,20 @@ export const enum InitPhaseState { 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. * @@ -438,21 +458,21 @@ export interface TView { 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. * * Even indices: Directive index * 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 * Odd indices: Hook function */ - checkHooks: HookData|null; + preOrderCheckHooks: HookData|null; /** * 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. * - * Even indices: Directive index - * Odd indices: Hook function + * For each node of the view, the following data is stored: + * 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))[]; diff --git a/packages/core/src/render3/jit/environment.ts b/packages/core/src/render3/jit/environment.ts index b99c4a112d..780a45c1a6 100644 --- a/packages/core/src/render3/jit/environment.ts +++ b/packages/core/src/render3/jit/environment.ts @@ -99,6 +99,7 @@ export const angularCoreEnv: {[name: string]: Function} = { 'ɵelementStylingMap': r3.elementStylingMap, 'ɵelementStyleProp': r3.elementStyleProp, 'ɵelementStylingApply': r3.elementStylingApply, + 'ɵflushHooksUpTo': r3.flushHooksUpTo, 'ɵtemplate': r3.template, 'ɵtext': r3.text, 'ɵtextBinding': r3.textBinding, diff --git a/packages/core/src/render3/state.ts b/packages/core/src/render3/state.ts index 19764f20ce..54021c6b36 100644 --- a/packages/core/src/render3/state.ts +++ b/packages/core/src/render3/state.ts @@ -13,6 +13,7 @@ import {executeHooks} from './hooks'; import {ComponentDef, DirectiveDef} from './interfaces/definition'; import {TElementNode, TNode, TViewNode} from './interfaces/node'; 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; } else { try { + resetPreOrderHookFlags(lView); executeHooks( lView, tView.viewHooks, tView.viewCheckHooks, checkNoChangesMode, - InitPhaseState.AfterViewInitHooksToBeRun); + InitPhaseState.AfterViewInitHooksToBeRun, undefined); } finally { // Views are clean and in update mode after being checked, so these bits are cleared lView[FLAGS] &= ~(LViewFlags.Dirty | LViewFlags.FirstLViewPass); diff --git a/packages/core/src/render3/util/view_utils.ts b/packages/core/src/render3/util/view_utils.ts index 36ecc7f11e..b79a179187 100644 --- a/packages/core/src/render3/util/view_utils.ts +++ b/packages/core/src/render3/util/view_utils.ts @@ -13,7 +13,7 @@ import {ComponentDef, DirectiveDef} from '../interfaces/definition'; import {TNode, TNodeFlags} from '../interfaces/node'; import {RNode} from '../interfaces/renderer'; 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 { 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; +} diff --git a/packages/core/test/acceptance/exports_spec.ts b/packages/core/test/acceptance/exports_spec.ts index 42bc020b58..63d3a0a485 100644 --- a/packages/core/test/acceptance/exports_spec.ts +++ b/packages/core/test/acceptance/exports_spec.ts @@ -6,14 +6,18 @@ * 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 {onlyInIvy} from '@angular/private/testing'; +import {modifiedInIvy, onlyInIvy} from '@angular/private/testing'; describe('exports', () => { beforeEach(() => { - TestBed.configureTestingModule( - {declarations: [AppComp, ComponentToReference, DirToReference, DirWithCompInput]}); + TestBed.configureTestingModule({ + declarations: [ + AppComp, ComponentToReference, DirToReference, DirToReferenceWithPreOrderHooks, + DirWithCompInput + ] + }); }); it('should support export of DOM element', () => { @@ -36,6 +40,68 @@ describe('exports', () => { expect(fixture.nativeElement.innerHTML).toEqual('
Drew'); }); + describe('input changes in hooks', () => { + it('should support forward reference', () => { + const fixture = initWithTemplate( + AppComp, '
{{ myDir.name }}'); + fixture.detectChanges(); + expect(fixture.nativeElement.innerHTML) + .toEqual('
Drew!?@'); + }); + + modifiedInIvy('Supporting input changes in hooks is limited in Ivy') + .it('should support backward reference', () => { + const fixture = initWithTemplate( + AppComp, '{{ myDir.name }}
'); + fixture.detectChanges(); + expect(fixture.nativeElement.innerHTML) + .toEqual('Drew!?@
'); + }); + + onlyInIvy('Supporting input changes in hooks is limited in Ivy') + .it('should not support backward reference', () => { + expect(() => { + const fixture = initWithTemplate( + AppComp, + '{{ myDir.name }}
'); + 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, + '
'); + fixture.detectChanges(); + expect(fixture.nativeElement.innerHTML) + .toEqual( + '
'); + }); + + onlyInIvy('Supporting input changes in hooks is limited in Ivy') + .it('should not support reference on the same node', () => { + expect(() => { + const fixture = initWithTemplate( + AppComp, + '
'); + 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, '
'); + fixture.detectChanges(); + expect(fixture.nativeElement.innerHTML) + .toEqual('
'); + }); + }); + onlyInIvy('Different error message is thrown in View Engine') .it('should throw if export name is not found', () => { expect(() => { @@ -95,3 +161,12 @@ class DirToReference { class DirWithCompInput { @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 += '@'; } +} diff --git a/packages/core/test/acceptance/lifecycle_spec.ts b/packages/core/test/acceptance/lifecycle_spec.ts index 3e32089dce..f5ce6fd7d8 100644 --- a/packages/core/test/acceptance/lifecycle_spec.ts +++ b/packages/core/test/acceptance/lifecycle_spec.ts @@ -6,7 +6,7 @@ * 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'; describe('ngOnChanges', () => { @@ -56,4 +56,116 @@ describe('ngOnChanges', () => { fixture.detectChanges(); expect(log).toEqual(['c: 0 -> 3']); }); -}); \ No newline at end of file +}); + +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: '
'}) + 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: '
' + }) + 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']); +}); diff --git a/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json b/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json index d0d82ba7e6..4da4db673a 100644 --- a/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json +++ b/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json @@ -107,6 +107,9 @@ { "name": "PARENT_INJECTOR" }, + { + "name": "PREORDER_HOOK_FLAGS" + }, { "name": "QUERIES" }, @@ -194,6 +197,9 @@ { "name": "cacheMatchingLocalNames" }, + { + "name": "callHook" + }, { "name": "callHooks" }, @@ -282,7 +288,7 @@ "name": "executeHooks" }, { - "name": "executeInitHooks" + "name": "executePreOrderHooks" }, { "name": "executeViewQueryFn" @@ -599,6 +605,9 @@ { "name": "resetComponentState" }, + { + "name": "resetPreOrderHookFlags" + }, { "name": "resolveDirectives" }, diff --git a/packages/core/test/bundling/hello_world/bundle.golden_symbols.json b/packages/core/test/bundling/hello_world/bundle.golden_symbols.json index 353e2ea179..3a8ba6079a 100644 --- a/packages/core/test/bundling/hello_world/bundle.golden_symbols.json +++ b/packages/core/test/bundling/hello_world/bundle.golden_symbols.json @@ -92,6 +92,9 @@ { "name": "PARENT_INJECTOR" }, + { + "name": "PREORDER_HOOK_FLAGS" + }, { "name": "RENDERER" }, @@ -149,6 +152,9 @@ { "name": "bloomAdd" }, + { + "name": "callHook" + }, { "name": "callHooks" }, @@ -207,7 +213,7 @@ "name": "executeHooks" }, { - "name": "executeInitHooks" + "name": "executePreOrderHooks" }, { "name": "executeViewQueryFn" @@ -428,6 +434,9 @@ { "name": "resetComponentState" }, + { + "name": "resetPreOrderHookFlags" + }, { "name": "setBindingRoot" }, diff --git a/packages/core/test/bundling/todo/bundle.golden_symbols.json b/packages/core/test/bundling/todo/bundle.golden_symbols.json index a279b0e311..03caa09233 100644 --- a/packages/core/test/bundling/todo/bundle.golden_symbols.json +++ b/packages/core/test/bundling/todo/bundle.golden_symbols.json @@ -179,6 +179,9 @@ { "name": "PARENT_INJECTOR" }, + { + "name": "PREORDER_HOOK_FLAGS" + }, { "name": "QUERIES" }, @@ -431,6 +434,9 @@ { "name": "cacheMatchingLocalNames" }, + { + "name": "callHook" + }, { "name": "callHooks" }, @@ -584,15 +590,15 @@ { "name": "executeHooks" }, - { - "name": "executeInitHooks" - }, { "name": "executeNodeAction" }, { "name": "executeOnDestroys" }, + { + "name": "executePreOrderHooks" + }, { "name": "executeViewQueryFn" }, @@ -620,6 +626,9 @@ { "name": "findViaComponent" }, + { + "name": "flushHooksUpTo" + }, { "name": "forwardRef" }, @@ -1157,6 +1166,9 @@ { "name": "resetComponentState" }, + { + "name": "resetPreOrderHookFlags" + }, { "name": "resolveDirectives" }, diff --git a/packages/core/test/render3/lifecycle_spec.ts b/packages/core/test/render3/lifecycle_spec.ts index c721ff6613..72ebed8263 100644 --- a/packages/core/test/render3/lifecycle_spec.ts +++ b/packages/core/test/render3/lifecycle_spec.ts @@ -9,7 +9,7 @@ import {ComponentFactoryResolver, OnDestroy, SimpleChange, SimpleChanges, ViewContainerRef} from '../../src/core'; 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 {NgIf} from './common_with_def'; @@ -139,6 +139,7 @@ describe('lifecycles', () => { } if (rf & RenderFlags.Update) { elementProperty(0, 'val', 1); + flushHooksUpTo(1); elementProperty(1, 'val', 2); } }, 2, 0, directives); @@ -289,8 +290,11 @@ describe('lifecycles', () => { } if (rf & RenderFlags.Update) { elementProperty(0, 'val', 1); + flushHooksUpTo(1); elementProperty(1, 'val', 1); + flushHooksUpTo(2); elementProperty(2, 'val', 2); + flushHooksUpTo(3); elementProperty(3, 'val', 2); } }, 4, 0, directives); @@ -345,6 +349,7 @@ describe('lifecycles', () => { } if (rf & RenderFlags.Update) { elementProperty(0, 'val', 1); + flushHooksUpTo(2); elementProperty(2, 'val', 5); containerRefreshStart(1); { @@ -385,6 +390,7 @@ describe('lifecycles', () => { } if (rf & RenderFlags.Update) { elementProperty(0, 'val', 1); + flushHooksUpTo(2); elementProperty(2, 'val', 5); containerRefreshStart(1); { @@ -623,6 +629,7 @@ describe('lifecycles', () => { } if (rf & RenderFlags.Update) { elementProperty(0, 'val', 1); + flushHooksUpTo(3); elementProperty(3, 'val', 4); containerRefreshStart(2); { @@ -746,6 +753,7 @@ describe('lifecycles', () => { } if (rf & RenderFlags.Update) { elementProperty(0, 'val', 1); + flushHooksUpTo(2); elementProperty(2, 'val', 2); } }, 4, 0, directives); @@ -814,8 +822,11 @@ describe('lifecycles', () => { } if (rf & RenderFlags.Update) { elementProperty(0, 'val', 1); + flushHooksUpTo(1); elementProperty(1, 'val', 1); + flushHooksUpTo(3); elementProperty(3, 'val', 2); + flushHooksUpTo(4); elementProperty(4, 'val', 2); } }, 6, 0, directives); @@ -844,6 +855,7 @@ describe('lifecycles', () => { } if (rf & RenderFlags.Update) { elementProperty(0, 'val', 1); + flushHooksUpTo(3); elementProperty(3, 'val', 4); containerRefreshStart(2); { @@ -1091,6 +1103,7 @@ describe('lifecycles', () => { } if (rf & RenderFlags.Update) { elementProperty(0, 'val', 1); + flushHooksUpTo(1); elementProperty(1, 'val', 2); } }, 2, 0, defs); @@ -1138,8 +1151,11 @@ describe('lifecycles', () => { } if (rf & RenderFlags.Update) { elementProperty(0, 'val', 1); + flushHooksUpTo(1); elementProperty(1, 'val', 1); + flushHooksUpTo(2); elementProperty(2, 'val', 2); + flushHooksUpTo(3); elementProperty(3, 'val', 2); } }, 4, 0, defs); @@ -1162,6 +1178,7 @@ describe('lifecycles', () => { } if (rf & RenderFlags.Update) { elementProperty(0, 'val', bind(ctx.val)); + flushHooksUpTo(1); elementProperty(1, 'val', bind(ctx.val)); } }, 2, 2, [Comp, ProjectedComp]); @@ -1177,6 +1194,7 @@ describe('lifecycles', () => { } if (rf & RenderFlags.Update) { elementProperty(0, 'val', 1); + flushHooksUpTo(1); elementProperty(1, 'val', 2); } }, 2, 0, [ParentComp]); @@ -1201,6 +1219,7 @@ describe('lifecycles', () => { } if (rf & RenderFlags.Update) { elementProperty(0, 'val', 1); + flushHooksUpTo(2); elementProperty(2, 'val', 4); containerRefreshStart(1); { @@ -1240,6 +1259,7 @@ describe('lifecycles', () => { } if (rf & RenderFlags.Update) { elementProperty(0, 'val', 1); + flushHooksUpTo(2); elementProperty(2, 'val', 4); containerRefreshStart(1); { @@ -1325,6 +1345,7 @@ describe('lifecycles', () => { } if (rf & RenderFlags.Update) { elementProperty(0, 'val', 1); + flushHooksUpTo(2); elementProperty(2, 'val', 4); containerRefreshStart(1); { @@ -1486,6 +1507,7 @@ describe('lifecycles', () => { } if (rf1 & RenderFlags.Update) { elementProperty(0, 'val', bind('1')); + flushHooksUpTo(1); elementProperty(1, 'val', bind('2')); } embeddedViewEnd(); @@ -1602,8 +1624,11 @@ describe('lifecycles', () => { } if (rf1 & RenderFlags.Update) { elementProperty(0, 'val', 1); + flushHooksUpTo(1); elementProperty(1, 'val', 1); + flushHooksUpTo(2); elementProperty(2, 'val', 2); + flushHooksUpTo(3); elementProperty(3, 'val', 2); } embeddedViewEnd(); @@ -1648,6 +1673,7 @@ describe('lifecycles', () => { } if (rf1 & RenderFlags.Update) { elementProperty(0, 'val', bind('1')); + flushHooksUpTo(2); elementProperty(2, 'val', bind('3')); containerRefreshStart(1); { @@ -1741,6 +1767,7 @@ describe('lifecycles', () => { } if (rf1 & RenderFlags.Update) { elementProperty(0, 'val', bind('1')); + flushHooksUpTo(2); elementProperty(2, 'val', bind('5')); containerRefreshStart(1); { @@ -2134,6 +2161,7 @@ describe('lifecycles', () => { if (rf & RenderFlags.Update) { elementProperty(0, 'val1', bind(1)); elementProperty(0, 'publicVal2', bind(1)); + flushHooksUpTo(1); elementProperty(1, 'val1', bind(2)); elementProperty(1, 'publicVal2', bind(2)); } @@ -2271,6 +2299,7 @@ describe('lifecycles', () => { if (rf & RenderFlags.Update) { elementProperty(0, 'val1', bind(1)); elementProperty(0, 'publicVal2', bind(1)); + flushHooksUpTo(1); elementProperty(1, 'val1', bind(2)); elementProperty(1, 'publicVal2', bind(2)); } @@ -2318,10 +2347,13 @@ describe('lifecycles', () => { if (rf & RenderFlags.Update) { elementProperty(0, 'val1', bind(1)); elementProperty(0, 'publicVal2', bind(1)); + flushHooksUpTo(1); elementProperty(1, 'val1', bind(2)); elementProperty(1, 'publicVal2', bind(2)); + flushHooksUpTo(2); elementProperty(2, 'val1', bind(3)); elementProperty(2, 'publicVal2', bind(3)); + flushHooksUpTo(3); elementProperty(3, 'val1', bind(4)); elementProperty(3, 'publicVal2', bind(4)); } @@ -2452,6 +2484,7 @@ describe('lifecycles', () => { if (rf & RenderFlags.Update) { elementProperty(0, 'val1', bind(1)); elementProperty(0, 'publicVal2', bind(1)); + flushHooksUpTo(2); elementProperty(2, 'val1', bind(5)); elementProperty(2, 'publicVal2', bind(5)); containerRefreshStart(1); @@ -2538,6 +2571,7 @@ describe('lifecycles', () => { if (rf & RenderFlags.Update) { elementProperty(0, 'val1', bind(1)); elementProperty(0, 'publicVal2', bind(1)); + flushHooksUpTo(2); elementProperty(2, 'val1', bind(5)); elementProperty(2, 'publicVal2', bind(5)); containerRefreshStart(1); @@ -2757,6 +2791,7 @@ describe('lifecycles', () => { // even though the *value* itself never changed. if (rf & RenderFlags.Update) { elementProperty(0, 'val', 1); + flushHooksUpTo(1); elementProperty(1, 'val', 2); } }, 2, 0, [Comp]); @@ -2800,6 +2835,7 @@ describe('lifecycles', () => { } if (rf & RenderFlags.Update) { elementProperty(0, 'val', 1); + flushHooksUpTo(1); elementProperty(1, 'val', 2); } }, 2, 0, [Parent]); @@ -2843,6 +2879,7 @@ describe('lifecycles', () => { element(1, 'view'); } if (rf & RenderFlags.Update) { + flushHooksUpTo(1); elementProperty(1, 'val', bind(ctx.val)); } }, 2, 1, [View]); @@ -2866,8 +2903,11 @@ describe('lifecycles', () => { } if (rf & RenderFlags.Update) { elementProperty(0, 'val', bind(1)); + flushHooksUpTo(1); elementProperty(1, 'val', bind(1)); + flushHooksUpTo(2); elementProperty(2, 'val', bind(2)); + flushHooksUpTo(3); elementProperty(3, 'val', bind(2)); } }, 4, 4, [Parent, Content]); diff --git a/packages/examples/forms/ts/simpleNgModel/e2e_test/simple_ng_model_spec.ts b/packages/examples/forms/ts/simpleNgModel/e2e_test/simple_ng_model_spec.ts index 1036b9161a..6241b2e2b9 100644 --- a/packages/examples/forms/ts/simpleNgModel/e2e_test/simple_ng_model_spec.ts +++ b/packages/examples/forms/ts/simpleNgModel/e2e_test/simple_ng_model_spec.ts @@ -6,43 +6,41 @@ * 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 {verifyNoBrowserErrors} from '../../../../test-utils'; -fixmeIvy('FW-1051: Directives are updated after the execution of the template function') - .describe('simpleNgModel example', () => { - afterEach(verifyNoBrowserErrors); - let input: ElementFinder; - let paragraphs: ElementArrayFinder; - let button: ElementFinder; +describe('simpleNgModel example', () => { + afterEach(verifyNoBrowserErrors); + let input: ElementFinder; + let paragraphs: ElementArrayFinder; + let button: ElementFinder; - beforeEach(() => { - browser.get('/simpleNgModel'); - input = element(by.css('input')); - paragraphs = element.all(by.css('p')); - button = element(by.css('button')); - }); + beforeEach(() => { + browser.get('/simpleNgModel'); + input = element(by.css('input')); + paragraphs = element.all(by.css('p')); + button = element(by.css('button')); + }); - it('should update the domain model as you type', () => { - input.click(); - input.sendKeys('Carson'); + it('should update the domain model as you type', () => { + input.click(); + 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', () => { - expect(paragraphs.get(1).getText()).toEqual('Valid: false'); - input.click(); - input.sendKeys('a'); + it('should report the validity correctly', () => { + expect(paragraphs.get(1).getText()).toEqual('Valid: false'); + input.click(); + 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', () => { - button.click(); - expect(input.getAttribute('value')).toEqual('Nancy'); - }); + it('should set the value by changing the domain model', () => { + button.click(); + expect(input.getAttribute('value')).toEqual('Nancy'); + }); - }); +});