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": {
"uncompressed": {
"runtime": 1440,
"main": 13659,
"main": 13921,
"polyfills": 38390
}
}

View File

@ -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));
}
},

View File

@ -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, " "));
}
}

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(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));

View File

@ -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, ""));
}
}

View File

@ -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));
}
}

View File

@ -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};

View File

@ -120,6 +120,11 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, 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<void>, 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<void>, 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<void>, 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<void>, 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<void>, 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<void>, 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<void>, 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<void>, 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<void>, 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 || []);
}

View File

@ -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,

View File

@ -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<T>(
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<any>): 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(

View File

@ -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<any>, tView: TView): void {
directiveIndex: number, directiveDef: DirectiveDef<any>, 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);
}
}

View File

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

View File

@ -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<T>(
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<T>(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<T>(
}
// 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<any>;
@ -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<T>(hostView: LView, component: T) {
const creationMode = isCreationMode(hostView);
try {
resetPreOrderHookFlags(hostView);
namespaceHTML();
creationMode && executeViewQueryFn(RenderFlags.Create, hostTView, component);
templateFn(getRenderFlags(hostView), component);

View File

@ -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<any> {
* 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))[];

View File

@ -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,

View File

@ -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);

View File

@ -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;
}

View File

@ -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('<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')
.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 += '@'; }
}

View File

@ -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']);
});
});
});
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": "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"
},

View File

@ -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"
},

View File

@ -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"
},

View File

@ -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]);

View File

@ -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');
});
});
});