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 845569198b..cf4d1325b4 100644 --- a/packages/compiler-cli/test/compliance/r3_compiler_compliance_spec.ts +++ b/packages/compiler-cli/test/compliance/r3_compiler_compliance_spec.ts @@ -317,7 +317,7 @@ describe('compiler compliance', () => { } if (rf & 2) { $r3$.ɵɵselect(0); - $r3$.ɵɵelementProperty(0, "id", $r3$.ɵɵbind(ctx.id)); + $r3$.ɵɵproperty("id", ctx.id); } } `; @@ -354,6 +354,14 @@ describe('compiler compliance', () => { } }; + /////////////// + // TODO(FW-1273): The code generated below is adding extra parens, and we need to stop + // generating those. + // + // For example: + // `$r3$.ɵɵproperty("ternary", (ctx.cond ? $r3$.ɵɵpureFunction1(8, $c0$, ctx.a): $c1$));` + /////////////// + const $e0_attrs$ = []; const factory = 'factory: function MyComponent_Factory(t) { return new (t || MyComponent)(); }'; @@ -365,10 +373,10 @@ describe('compiler compliance', () => { } if (rf & 2) { $r3$.ɵɵselect(0); - $r3$.ɵɵelementProperty(0, "ternary", $r3$.ɵɵbind((ctx.cond ? $r3$.ɵɵpureFunction1(8, $c0$, ctx.a): $c1$))); - $r3$.ɵɵelementProperty(0, "pipe", $r3$.ɵɵbind($r3$.ɵɵpipeBind3(1, 4, ctx.value, 1, 2))); - $r3$.ɵɵelementProperty(0, "and", $r3$.ɵɵbind((ctx.cond && $r3$.ɵɵpureFunction1(10, $c0$, ctx.b)))); - $r3$.ɵɵelementProperty(0, "or", $r3$.ɵɵbind((ctx.cond || $r3$.ɵɵpureFunction1(12, $c0$, ctx.c)))); + $r3$.ɵɵproperty("ternary", (ctx.cond ? $r3$.ɵɵpureFunction1(8, $c0$, ctx.a): $c1$)); + $r3$.ɵɵproperty("pipe", $r3$.ɵɵpipeBind3(1, 4, ctx.value, 1, 2)); + $r3$.ɵɵproperty("and", (ctx.cond && $r3$.ɵɵpureFunction1(10, $c0$, ctx.b))); + $r3$.ɵɵproperty("or", (ctx.cond || $r3$.ɵɵpureFunction1(12, $c0$, ctx.c))); } } `; @@ -880,7 +888,7 @@ describe('compiler compliance', () => { } if (rf & 2) { $r3$.ɵɵselect(0); - $r3$.ɵɵelementProperty(0, "names", $r3$.ɵɵbind($r3$.ɵɵpureFunction1(1, $e0_ff$, ctx.customName))); + $r3$.ɵɵproperty("names", $r3$.ɵɵpureFunction1(1, $e0_ff$, ctx.customName)); } }, directives: [MyComp], @@ -963,9 +971,8 @@ describe('compiler compliance', () => { } if (rf & 2) { $r3$.ɵɵselect(0); - $r3$.ɵɵelementProperty( - 0, "names", - $r3$.ɵɵbind($r3$.ɵɵpureFunctionV(1, $e0_ff$, [ctx.n0, ctx.n1, ctx.n2, ctx.n3, ctx.n4, ctx.n5, ctx.n6, ctx.n7, ctx.n8]))); + $r3$.ɵɵproperty("names", + $r3$.ɵɵpureFunctionV(1, $e0_ff$, [ctx.n0, ctx.n1, ctx.n2, ctx.n3, ctx.n4, ctx.n5, ctx.n6, ctx.n7, ctx.n8])); } }, directives: [MyComp], @@ -1028,7 +1035,7 @@ describe('compiler compliance', () => { } if (rf & 2) { $r3$.ɵɵselect(0); - $r3$.ɵɵelementProperty(0, "config", $r3$.ɵɵbind($r3$.ɵɵpureFunction1(1, $e0_ff$, ctx.name))); + $r3$.ɵɵproperty("config", $r3$.ɵɵpureFunction1(1, $e0_ff$, ctx.name)); } }, directives: [ObjectComp], @@ -1097,9 +1104,9 @@ describe('compiler compliance', () => { } if (rf & 2) { $r3$.ɵɵselect(0); - $r3$.ɵɵelementProperty( - 0, "config", - $r3$.ɵɵbind($r3$.ɵɵpureFunction2(5, $e0_ff_2$, ctx.name, $r3$.ɵɵpureFunction1(3, $e0_ff_1$, $r3$.ɵɵpureFunction1(1, $e0_ff$, ctx.duration))))); + $r3$.ɵɵproperty( + "config", + $r3$.ɵɵpureFunction2(5, $e0_ff_2$, ctx.name, $r3$.ɵɵpureFunction1(3, $e0_ff_1$, $r3$.ɵɵpureFunction1(1, $e0_ff$, ctx.duration)))); } }, directives: [NestedComp], @@ -1261,9 +1268,9 @@ describe('compiler compliance', () => { } if (rf & 2) { $r3$.ɵɵselect(0); - $r3$.ɵɵelementProperty(0, "ngIf", $r3$.ɵɵbind(ctx.visible)); + $r3$.ɵɵproperty("ngIf", ctx.visible); $r3$.ɵɵselect(1); - $r3$.ɵɵelementProperty(1, "ngIf", $r3$.ɵɵbind(ctx.visible)); + $r3$.ɵɵproperty("ngIf", ctx.visible); } } `; @@ -2350,7 +2357,7 @@ describe('compiler compliance', () => { if (rf & 2) { const $app$ = $i0$.ɵɵnextContext(); $r3$.ɵɵselect(3); - $i0$.ɵɵelementProperty(3, "ngIf", $i0$.ɵɵbind($app$.showing)); + $i0$.ɵɵproperty("ngIf", $app$.showing); } } @@ -2361,7 +2368,7 @@ describe('compiler compliance', () => { } if (rf & 2) { $i0$.ɵɵselect(0); - $i0$.ɵɵelementProperty(0, "ngForOf", $i0$.ɵɵbind(ctx.items)); + $i0$.ɵɵproperty("ngForOf", ctx.items); } }`; @@ -2442,9 +2449,9 @@ describe('compiler compliance', () => { } if (rf & 2) { $r3$.ɵɵselect(0); - $r3$.ɵɵelementProperty(0, "name", $r3$.ɵɵbind(ctx.name1)); + $r3$.ɵɵproperty("name", ctx.name1); $r3$.ɵɵselect(1); - $r3$.ɵɵelementProperty(1, "name", $r3$.ɵɵbind(ctx.name2)); + $r3$.ɵɵproperty("name", ctx.name2); } }, directives: [LifecycleComp], @@ -2576,7 +2583,7 @@ describe('compiler compliance', () => { } if (rf & 2) { $r3$.ɵɵselect(1); - $r3$.ɵɵelementProperty(1,"forOf",$r3$.ɵɵbind(ctx.items)); + $r3$.ɵɵproperty("forOf", ctx.items); } }, directives: function() { return [ForOfDirective]; }, @@ -2658,7 +2665,7 @@ describe('compiler compliance', () => { } if (rf & 2) { $r3$.ɵɵselect(1); - $r3$.ɵɵelementProperty(1, "forOf", $r3$.ɵɵbind(ctx.items)); + $r3$.ɵɵproperty("forOf", ctx.items); } }, directives: function() { return [ForOfDirective]; }, @@ -2743,7 +2750,7 @@ describe('compiler compliance', () => { $r3$.ɵɵselect(2); $r3$.ɵɵtextBinding(2, $r3$.ɵɵinterpolation1("", IDENT.name, "")); $r3$.ɵɵselect(4); - $r3$.ɵɵelementProperty(4, "forOf", $r3$.ɵɵbind(IDENT.infos)); + $r3$.ɵɵproperty("forOf", IDENT.infos); } } @@ -2762,7 +2769,7 @@ describe('compiler compliance', () => { } if (rf & 2) { $r3$.ɵɵselect(1); - $r3$.ɵɵelementProperty(1, "forOf", $r3$.ɵɵbind(ctx.items)); + $r3$.ɵɵproperty("forOf", ctx.items); } }, directives: function () { return [ForOfDirective]; }, 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 0b3189a2ca..5d44e679e3 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 @@ -82,7 +82,7 @@ describe('compiler compliance: bindings', () => { } if (rf & 2) { $i0$.ɵɵselect(0); - $i0$.ɵɵelementProperty(0, "title", $i0$.ɵɵbind($ctx$.title)); + $i0$.ɵɵproperty("title", $ctx$.title); } }`; const result = compile(files, angularFiles); @@ -174,7 +174,7 @@ describe('compiler compliance: bindings', () => { } if (rf & 2) { $i0$.ɵɵselect(0); - $i0$.ɵɵelementProperty(0, "for", $i0$.ɵɵbind(ctx.forValue)); + $i0$.ɵɵproperty("for", ctx.forValue); } }`; diff --git a/packages/compiler-cli/test/compliance/r3_view_compiler_directives_spec.ts b/packages/compiler-cli/test/compliance/r3_view_compiler_directives_spec.ts index 6632fdb85a..6ef22fdcf0 100644 --- a/packages/compiler-cli/test/compliance/r3_view_compiler_directives_spec.ts +++ b/packages/compiler-cli/test/compliance/r3_view_compiler_directives_spec.ts @@ -140,7 +140,7 @@ describe('compiler compliance: directives', () => { } if (rf & 2) { $r3$.ɵɵselect(0); - $r3$.ɵɵelementProperty(0, "someDirective", $r3$.ɵɵbind(true)); + $r3$.ɵɵproperty("someDirective", true); } }, … @@ -255,7 +255,7 @@ describe('compiler compliance: directives', () => { } if (rf & 2) { $r3$.ɵɵselect(0); - $r3$.ɵɵelementProperty(0, "ngIf", $r3$.ɵɵbind(ctx.showing)); + $r3$.ɵɵproperty("ngIf", ctx.showing); } }, … @@ -303,7 +303,7 @@ describe('compiler compliance: directives', () => { } if (rf & 2) { $r3$.ɵɵselect(0); - $r3$.ɵɵelementProperty(0, "someDirective", $r3$.ɵɵbind(true)); + $r3$.ɵɵproperty("someDirective", true); } }, … 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 dfd6bb49bb..c18f7c793b 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 @@ -580,7 +580,7 @@ describe('i18n support in the view compiler', () => { } if (rf & 2) { $r3$.ɵɵselect(0); - $r3$.ɵɵelementProperty(0, "ngForOf", $r3$.ɵɵbind(ctx.items)); + $r3$.ɵɵproperty("ngForOf", ctx.items); } } `; @@ -767,7 +767,7 @@ describe('i18n support in the view compiler', () => { } if (rf & 2) { $r3$.ɵɵselect(0); - $r3$.ɵɵelementProperty(0, "ngForOf", $r3$.ɵɵbind(ctx.items)); + $r3$.ɵɵproperty("ngForOf", ctx.items); } } `; @@ -1429,7 +1429,7 @@ describe('i18n support in the view compiler', () => { } if (rf & 2) { $r3$.ɵɵselect(2); - $r3$.ɵɵelementProperty(2, "ngIf", $r3$.ɵɵbind(ctx.visible)); + $r3$.ɵɵproperty("ngIf", ctx.visible); } } `; @@ -1491,9 +1491,9 @@ describe('i18n support in the view compiler', () => { } if (rf & 2) { $r3$.ɵɵselect(1); - $r3$.ɵɵelementProperty(1, "ngIf", $r3$.ɵɵbind(ctx.visible)); + $r3$.ɵɵproperty("ngIf", ctx.visible); $r3$.ɵɵselect(2); - $r3$.ɵɵelementProperty(2, "ngIf", $r3$.ɵɵbind(ctx.visible)); + $r3$.ɵɵproperty("ngIf", ctx.visible); } } `; @@ -1558,7 +1558,7 @@ describe('i18n support in the view compiler', () => { if (rf & 2) { const $ctx_r0$ = $r3$.ɵɵnextContext(); $r3$.ɵɵselect(4); - $r3$.ɵɵelementProperty(4, "ngIf", $r3$.ɵɵbind($ctx_r0$.exists)); + $r3$.ɵɵproperty("ngIf", $ctx_r0$.exists); $r3$.ɵɵi18nExp($r3$.ɵɵbind($ctx_r0$.valueA)); $r3$.ɵɵi18nExp($r3$.ɵɵbind($r3$.ɵɵpipeBind1(3, 3, $ctx_r0$.valueB))); $r3$.ɵɵi18nApply(0); @@ -1628,9 +1628,9 @@ describe('i18n support in the view compiler', () => { } if (rf & 2) { $r3$.ɵɵselect(2); - $r3$.ɵɵelementProperty(2, "ngIf", $r3$.ɵɵbind(ctx.visible)); + $r3$.ɵɵproperty("ngIf", ctx.visible); $r3$.ɵɵselect(3); - $r3$.ɵɵelementProperty(3, "ngIf", $r3$.ɵɵbind(!ctx.visible)); + $r3$.ɵɵproperty("ngIf", !ctx.visible); } } `; @@ -1686,7 +1686,7 @@ describe('i18n support in the view compiler', () => { } if (rf & 2) { $r3$.ɵɵselect(0); - $r3$.ɵɵelementProperty(0, "ngIf", $r3$.ɵɵbind(ctx.visible)); + $r3$.ɵɵproperty("ngIf", ctx.visible); } } `; @@ -2597,9 +2597,9 @@ describe('i18n support in the view compiler', () => { $r3$.ɵɵi18nExp($r3$.ɵɵbind(ctx.gender)); $r3$.ɵɵi18nApply(1); $r3$.ɵɵselect(2); - $r3$.ɵɵelementProperty(2, "ngIf", $r3$.ɵɵbind(ctx.visible)); + $r3$.ɵɵproperty("ngIf", ctx.visible); $r3$.ɵɵselect(3); - $r3$.ɵɵelementProperty(3, "ngIf", $r3$.ɵɵbind(ctx.available)); + $r3$.ɵɵproperty("ngIf", ctx.available); } } `; @@ -2935,7 +2935,7 @@ describe('i18n support in the view compiler', () => { } if (rf & 2) { $r3$.ɵɵselect(3); - $r3$.ɵɵelementProperty(3, "ngIf", $r3$.ɵɵbind(ctx.visible)); + $r3$.ɵɵproperty("ngIf", ctx.visible); $r3$.ɵɵi18nExp($r3$.ɵɵbind(ctx.gender)); $r3$.ɵɵi18nExp($r3$.ɵɵbind(ctx.gender)); $r3$.ɵɵi18nApply(1); @@ -3075,7 +3075,7 @@ describe('i18n support in the view compiler', () => { } if (rf & 2) { $r3$.ɵɵselect(2); - $r3$.ɵɵelementProperty(2, "ngIf", $r3$.ɵɵbind(ctx.ageVisible)); + $r3$.ɵɵproperty("ngIf", ctx.ageVisible); $r3$.ɵɵi18nExp($r3$.ɵɵbind(ctx.gender)); $r3$.ɵɵi18nApply(1); } @@ -3174,7 +3174,7 @@ describe('i18n support in the view compiler', () => { } if (rf & 2) { $r3$.ɵɵselect(2); - $r3$.ɵɵelementProperty(2, "ngIf", $r3$.ɵɵbind(ctx.ageVisible)); + $r3$.ɵɵproperty("ngIf", ctx.ageVisible); $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_listener_spec.ts b/packages/compiler-cli/test/compliance/r3_view_compiler_listener_spec.ts index 520764a787..c7368bfda5 100644 --- a/packages/compiler-cli/test/compliance/r3_view_compiler_listener_spec.ts +++ b/packages/compiler-cli/test/compliance/r3_view_compiler_listener_spec.ts @@ -164,7 +164,7 @@ describe('compiler compliance: listen()', () => { } if (rf & 2) { $i0$.ɵɵselect(0); - $i0$.ɵɵelementProperty(0, "ngIf", $i0$.ɵɵbind(ctx.showing)); + $i0$.ɵɵproperty("ngIf", ctx.showing); } } `; diff --git a/packages/compiler-cli/test/compliance/r3_view_compiler_spec.ts b/packages/compiler-cli/test/compliance/r3_view_compiler_spec.ts index 37ca5e8d0e..10dbc48055 100644 --- a/packages/compiler-cli/test/compliance/r3_view_compiler_spec.ts +++ b/packages/compiler-cli/test/compliance/r3_view_compiler_spec.ts @@ -147,8 +147,8 @@ describe('r3_view_compiler', () => { } if (rf & 2) { $i0$.ɵɵselect(0); - $i0$.ɵɵelementProperty(0, "@attr", …); - $i0$.ɵɵelementProperty(0, "@binding", …); + $i0$.ɵɵproperty("@attr", …); + $i0$.ɵɵproperty("@binding", …); } }`; const result = compile(files, angularFiles); @@ -179,7 +179,7 @@ describe('r3_view_compiler', () => { $i0$.ɵɵelementStart(0, "div"); … $i0$.ɵɵselect(0); - $i0$.ɵɵelementProperty(0, "@mySelector", …); + $i0$.ɵɵproperty("@mySelector", …); } }`; const result = compile(files, angularFiles); 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 abce0a642e..344ee23667 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,11 +227,11 @@ describe('compiler compliance: styling', () => { } if (rf & 2) { $r3$.ɵɵselect(0); - $r3$.ɵɵelementProperty(0, "@foo", $r3$.ɵɵbind(ctx.exp)); + $r3$.ɵɵproperty("@foo", ctx.exp); $r3$.ɵɵselect(1); - $r3$.ɵɵelementProperty(1, "@bar", $r3$.ɵɵbind(undefined)); + $r3$.ɵɵproperty("@bar", undefined); $r3$.ɵɵselect(2); - $r3$.ɵɵelementProperty(2, "@baz", $r3$.ɵɵbind(undefined)); + $r3$.ɵɵproperty("@baz", undefined); } }, encapsulation: 2 @@ -289,7 +289,7 @@ describe('compiler compliance: styling', () => { $r3$.ɵɵelementEnd(); } if (rf & 2) { $r3$.ɵɵselect(0); - $r3$.ɵɵelementProperty(0, "@myAnimation", $r3$.ɵɵbind(ctx.exp)); + $r3$.ɵɵproperty("@myAnimation", ctx.exp); } }, encapsulation: 2, 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 e9eb9a4b3c..cfed15fd20 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 @@ -76,7 +76,7 @@ describe('compiler compliance: template', () => { const $outer1$ = $i0$.ɵɵnextContext().$implicit; const $myComp1$ = $i0$.ɵɵnextContext(); $i0$.ɵɵselect(0); - $i0$.ɵɵelementProperty(0, "title", $i0$.ɵɵbind($myComp1$.format($outer1$, $middle1$, $inner1$, $myComp1$.component))); + $i0$.ɵɵproperty("title", $myComp1$.format($outer1$, $middle1$, $inner1$, $myComp1$.component)); $r3$.ɵɵselect(1); $i0$.ɵɵtextBinding(1, $i0$.ɵɵinterpolation1(" ", $myComp1$.format($outer1$, $middle1$, $inner1$, $myComp1$.component), " ")); } @@ -91,7 +91,7 @@ describe('compiler compliance: template', () => { if (rf & 2) { const $myComp2$ = $i0$.ɵɵnextContext(2); $r3$.ɵɵselect(1); - $i0$.ɵɵelementProperty(1, "ngForOf", $i0$.ɵɵbind($myComp2$.items)); + $i0$.ɵɵproperty("ngForOf", $myComp2$.items); } } @@ -104,7 +104,7 @@ describe('compiler compliance: template', () => { if (rf & 2) { const $outer2$ = ctx.$implicit; $r3$.ɵɵselect(1); - $i0$.ɵɵelementProperty(1, "ngForOf", $i0$.ɵɵbind($outer2$.items)); + $i0$.ɵɵproperty("ngForOf", $outer2$.items); } } // ... @@ -114,7 +114,7 @@ describe('compiler compliance: template', () => { } if (rf & 2) { $i0$.ɵɵselect(0); - $i0$.ɵɵelementProperty(0, "ngForOf", $i0$.ɵɵbind(ctx.items)); + $i0$.ɵɵproperty("ngForOf", ctx.items); } }`; @@ -171,7 +171,7 @@ describe('compiler compliance: template', () => { } if (rf & 2) { $r3$.ɵɵselect(0); - $r3$.ɵɵelementProperty(0, "ngForOf", $r3$.ɵɵbind(ctx._data)); + $r3$.ɵɵproperty("ngForOf", ctx._data); } } `; @@ -225,7 +225,7 @@ describe('compiler compliance: template', () => { } if (rf & 2) { $i0$.ɵɵselect(0); - $i0$.ɵɵelementProperty(0, "ngForOf", $i0$.ɵɵbind(ctx.items)); + $i0$.ɵɵproperty("ngForOf", ctx.items); } }`; @@ -285,7 +285,7 @@ describe('compiler compliance: template', () => { if (rf & 2) { const $app$ = $i0$.ɵɵnextContext(); $r3$.ɵɵselect(1); - $i0$.ɵɵelementProperty(1, "ngIf", $i0$.ɵɵbind($app$.showing)); + $i0$.ɵɵproperty("ngIf", $app$.showing); } } @@ -296,7 +296,7 @@ describe('compiler compliance: template', () => { } if (rf & 2) { $i0$.ɵɵselect(0); - $i0$.ɵɵelementProperty(0, "ngForOf", $i0$.ɵɵbind(ctx.items)); + $i0$.ɵɵproperty("ngForOf", ctx.items); } }`; @@ -356,7 +356,7 @@ describe('compiler compliance: template', () => { if (rf & 2) { const $middle$ = ctx.$implicit; $r3$.ɵɵselect(1); - $i0$.ɵɵelementProperty(1, "ngForOf", $i0$.ɵɵbind($middle$.items)); + $i0$.ɵɵproperty("ngForOf", $middle$.items); } } @@ -369,7 +369,7 @@ describe('compiler compliance: template', () => { if (rf & 2) { const $outer$ = ctx.$implicit; $r3$.ɵɵselect(1); - $i0$.ɵɵelementProperty(1, "ngForOf", $i0$.ɵɵbind($outer$.items)); + $i0$.ɵɵproperty("ngForOf", $outer$.items); } } // ... @@ -379,7 +379,7 @@ describe('compiler compliance: template', () => { } if (rf & 2) { $i0$.ɵɵselect(0); - $i0$.ɵɵelementProperty(0, "ngForOf", $i0$.ɵɵbind(ctx.items)); + $i0$.ɵɵproperty("ngForOf", ctx.items); } }`; @@ -426,7 +426,7 @@ describe('compiler compliance: template', () => { } if (rf & 2) { $i0$.ɵɵselect(0); - $i0$.ɵɵelementProperty(0, "boundAttr", $i0$.ɵɵbind(ctx.b)); + $i0$.ɵɵproperty("boundAttr", ctx.b); } }`; @@ -669,7 +669,7 @@ describe('compiler compliance: template', () => { $i0$.ɵɵpipe(1, "pipe"); } if (rf & 2) { $i0$.ɵɵselect(0); - $i0$.ɵɵelementProperty(0, "ngIf", $i0$.ɵɵbind($i0$.ɵɵpipeBind1(1, 1, ctx.val))); + $i0$.ɵɵproperty("ngIf", $i0$.ɵɵpipeBind1(1, 1, ctx.val)); } }`; diff --git a/packages/compiler-cli/test/ngtsc/template_mapping_spec.ts b/packages/compiler-cli/test/ngtsc/template_mapping_spec.ts index f72c191f98..1e24656b78 100644 --- a/packages/compiler-cli/test/ngtsc/template_mapping_spec.ts +++ b/packages/compiler-cli/test/ngtsc/template_mapping_spec.ts @@ -104,13 +104,14 @@ describe('template source-mapping', () => { }); expect(mappings).toContain({ source: '[attr]="name"', - generated: 'i0.ɵɵelementProperty(0, "attr", i0.ɵɵbind(ctx.name))', + generated: 'i0.ɵɵproperty("attr", ctx.name)', sourceUrl: '../test.ts' }); }); it('should map a complex input binding expression', () => { const mappings = compileAndMap('
'); + expect(mappings).toContain({ source: '
', generated: 'i0.ɵɵelement(0, "div", _c0)', @@ -118,7 +119,7 @@ describe('template source-mapping', () => { }); expect(mappings).toContain({ source: '[attr]="greeting + name"', - generated: 'i0.ɵɵelementProperty(0, "attr", i0.ɵɵbind((ctx.greeting + ctx.name)))', + generated: 'i0.ɵɵproperty("attr", (ctx.greeting + ctx.name))', sourceUrl: '../test.ts' }); }); @@ -132,7 +133,7 @@ describe('template source-mapping', () => { }); expect(mappings).toContain({ source: 'bind-attr="name"', - generated: 'i0.ɵɵelementProperty(0, "attr", i0.ɵɵbind(ctx.name))', + generated: 'i0.ɵɵproperty("attr", ctx.name)', sourceUrl: '../test.ts' }); }); diff --git a/packages/compiler/src/render3/r3_identifiers.ts b/packages/compiler/src/render3/r3_identifiers.ts index 80f37838c2..e79dbef840 100644 --- a/packages/compiler/src/render3/r3_identifiers.ts +++ b/packages/compiler/src/render3/r3_identifiers.ts @@ -123,6 +123,8 @@ export class Identifiers { static pipeBind4: o.ExternalReference = {name: 'ɵɵpipeBind4', moduleName: CORE}; static pipeBindV: o.ExternalReference = {name: 'ɵɵpipeBindV', moduleName: CORE}; + static property: o.ExternalReference = {name: 'ɵɵproperty', moduleName: CORE}; + static i18n: o.ExternalReference = {name: 'ɵɵi18n', moduleName: CORE}; static i18nAttributes: o.ExternalReference = {name: 'ɵɵi18nAttributes', moduleName: CORE}; static i18nExp: o.ExternalReference = {name: 'ɵɵi18nExp', moduleName: CORE}; diff --git a/packages/compiler/src/render3/view/template.ts b/packages/compiler/src/render3/view/template.ts index f273660b51..6329fb2565 100644 --- a/packages/compiler/src/render3/view/template.ts +++ b/packages/compiler/src/render3/view/template.ts @@ -39,6 +39,7 @@ import {I18N_ICU_MAPPING_PREFIX, TRANSLATION_PREFIX, assembleBoundTextPlaceholde import {Instruction, StylingBuilder} from './styling_builder'; import {CONTEXT_NAME, IMPLICIT_REFERENCE, NON_BINDABLE_ATTR, REFERENCE_PREFIX, RENDER_FLAGS, asLiteral, getAttrsForDirectiveMatching, invalid, trimTrailingNulls, unsupported} from './util'; + // Default selector used by `` if none specified const DEFAULT_NG_CONTENT_SELECTOR = '*'; @@ -52,20 +53,6 @@ const NG_PROJECT_AS_ATTR_NAME = 'ngProjectAs'; const GLOBAL_TARGET_RESOLVERS = new Map( [['window', R3.resolveWindow], ['document', R3.resolveDocument], ['body', R3.resolveBody]]); -function mapBindingToInstruction(type: BindingType): o.ExternalReference|undefined { - switch (type) { - case BindingType.Property: - case BindingType.Animation: - return R3.elementProperty; - case BindingType.Class: - return R3.elementClassProp; - case BindingType.Attribute: - return R3.elementAttribute; - default: - return undefined; - } -} - // if (rf & flags) { .. } export function renderFlagCheckIfStmt( flags: core.RenderFlags, statements: o.Statement[]): o.IfStmt { @@ -707,13 +694,12 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver // the reason why `undefined` is used is because the renderer understands this as a // special value to symbolize that there is no RHS to this binding // TODO (matsko): revisit this once FW-959 is approached - const emptyValueBindInstruction = o.importExpr(R3.bind).callFn([o.literal(undefined)]); + const emptyValueBindInstruction = o.literal(undefined); // Generate element input bindings allOtherInputs.forEach((input: t.BoundAttribute) => { - - const instruction = mapBindingToInstruction(input.type); - if (input.type === BindingType.Animation) { + const inputType = input.type; + if (inputType === BindingType.Animation) { const value = input.value.visit(this._valueConverter); // animation bindings can be presented in the following formats: // 1. [@binding]="fooExp" @@ -727,13 +713,15 @@ 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(elementIndex, input.sourceSpan, R3.elementProperty, () => { + + this.updateInstruction(elementIndex, input.sourceSpan, R3.property, () => { return [ - o.literal(elementIndex), o.literal(bindingName), - (hasValue ? this.convertPropertyBinding(implicit, value) : emptyValueBindInstruction) + o.literal(bindingName), + (hasValue ? this.convertPropertyBinding(implicit, value, /* skipBindFn */ true) : + emptyValueBindInstruction), ]; }); - } else if (instruction) { + } else { // we must skip attributes with associated i18n context, since these attributes are handled // separately and corresponding `i18nExp` and `i18nApply` instructions will be generated if (input.i18n) return; @@ -742,7 +730,7 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver if (value !== undefined) { const params: any[] = []; const [attrNamespace, attrName] = splitNsName(input.name); - const isAttributeBinding = input.type === BindingType.Attribute; + const isAttributeBinding = inputType === BindingType.Attribute; const sanitizationRef = resolveSanitizationFn(input.securityContext, isAttributeBinding); if (sanitizationRef) params.push(sanitizationRef); if (attrNamespace) { @@ -757,15 +745,34 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver } } this.allocateBindingSlots(value); - this.updateInstruction(elementIndex, input.sourceSpan, instruction, () => { - return [ - o.literal(elementIndex), o.literal(attrName), - this.convertPropertyBinding(implicit, value), ...params - ]; - }); + + if (inputType === BindingType.Property && !(value instanceof Interpolation)) { + // Bound, un-interpolated properties + this.updateInstruction(elementIndex, input.sourceSpan, R3.property, () => { + return [ + o.literal(attrName), this.convertPropertyBinding(implicit, value, true), ...params + ]; + }); + } else { + let instruction: any; + + if (inputType === BindingType.Property) { + // Interpolated properties + instruction = R3.elementProperty; + } else if (inputType === BindingType.Class) { + instruction = R3.elementClassProp; + } else { + instruction = R3.elementAttribute; + } + + this.updateInstruction(elementIndex, input.sourceSpan, instruction, () => { + return [ + o.literal(elementIndex), o.literal(attrName), + this.convertPropertyBinding(implicit, value), ...params + ]; + }); + } } - } else { - this._unsupported(`binding type ${input.type}`); } }); @@ -856,7 +863,7 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver return trimTrailingNulls(parameters); }); - // handle property bindings e.g. ɵɵelementProperty(1, 'ngForOf', ɵɵbind(ctx.items)); + // handle property bindings e.g. ɵɵproperty('ngForOf', ctx.items), et al; const context = o.variable(CONTEXT_NAME); this.templatePropertyBindings(template, templateIndex, context, template.templateAttrs); @@ -970,12 +977,9 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver if (input instanceof t.BoundAttribute) { const value = input.value.visit(this._valueConverter); this.allocateBindingSlots(value); - this.updateInstruction(templateIndex, template.sourceSpan, R3.elementProperty, () => { - return [ - o.literal(templateIndex), o.literal(input.name), - this.convertPropertyBinding(context, value) - ]; - }); + this.updateInstruction( + templateIndex, template.sourceSpan, R3.property, + () => [o.literal(input.name), this.convertPropertyBinding(context, value, true)]); } }); } diff --git a/packages/core/src/core_render3_private_export.ts b/packages/core/src/core_render3_private_export.ts index 2d9099f8d8..634120cfa0 100644 --- a/packages/core/src/core_render3_private_export.ts +++ b/packages/core/src/core_render3_private_export.ts @@ -89,6 +89,7 @@ export { ɵɵloadContentQuery, ɵɵelementEnd, ɵɵelementProperty, + ɵɵproperty, ɵɵcomponentHostSyntheticProperty, ɵɵcomponentHostSyntheticListener, ɵɵprojectionDef, diff --git a/packages/core/src/render3/instructions/property.ts b/packages/core/src/render3/instructions/property.ts index 9557c2f3cd..acb170f78d 100644 --- a/packages/core/src/render3/instructions/property.ts +++ b/packages/core/src/render3/instructions/property.ts @@ -5,6 +5,7 @@ * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ +import {assertNotEqual} from '../../util/assert'; import {bindingUpdated} from '../bindings'; import {SanitizerFn} from '../interfaces/sanitization'; import {BINDING_INDEX} from '../interfaces/view'; @@ -38,6 +39,7 @@ export function ɵɵproperty( propName: string, value: T, sanitizer?: SanitizerFn | null, nativeOnly?: boolean): TsickleIssue1009 { const index = getSelectedIndex(); + ngDevMode && assertNotEqual(index, -1, 'selected index cannot be -1'); const bindReconciledValue = ɵɵbind(value); elementPropertyInternal(index, propName, bindReconciledValue, sanitizer, nativeOnly); return ɵɵproperty; diff --git a/packages/core/src/render3/instructions/select.ts b/packages/core/src/render3/instructions/select.ts index 7df13cd745..1c77ac0176 100644 --- a/packages/core/src/render3/instructions/select.ts +++ b/packages/core/src/render3/instructions/select.ts @@ -36,7 +36,14 @@ export function ɵɵselect(index: number): void { ngDevMode && assertLessThan( index, getLView().length - HEADER_OFFSET, 'Should be within range for the view data'); - setSelectedIndex(index); const lView = getLView(); + + // Flush the initial hooks for elements in the view that have been added up to this point. executePreOrderHooks(lView, lView[TVIEW], getCheckNoChangesMode(), index); + + // We must set the selected index *after* running the hooks, because hooks may have side-effects + // that cause other template functions to run, thus updating the selected index, which is global + // state. If we run `setSelectedIndex` *before* we run the hooks, in some cases the selected index + // will be altered by the time we leave the `ɵɵselect` instruction. + setSelectedIndex(index); } diff --git a/packages/core/src/render3/instructions/shared.ts b/packages/core/src/render3/instructions/shared.ts index 1ef5613bd1..1ac4760bee 100644 --- a/packages/core/src/render3/instructions/shared.ts +++ b/packages/core/src/render3/instructions/shared.ts @@ -30,7 +30,7 @@ import {StylingContext} from '../interfaces/styling'; import {BINDING_INDEX, CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTEXT, DECLARATION_VIEW, ExpandoInstructions, FLAGS, HEADER_OFFSET, HOST, INJECTOR, InitPhaseState, LView, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, RENDERER_FACTORY, RootContext, RootContextFlags, SANITIZER, TData, TVIEW, TView, T_HOST} from '../interfaces/view'; import {assertNodeOfPossibleTypes, assertNodeType} from '../node_assert'; import {isNodeMatchingSelectorList} from '../node_selector_matcher'; -import {enterView, getBindingsEnabled, getCheckNoChangesMode, getIsParent, getLView, getNamespace, getPreviousOrParentTNode, incrementActiveDirectiveId, isCreationMode, leaveView, resetComponentState, setActiveHostElement, setBindingRoot, setCheckNoChangesMode, setCurrentDirectiveDef, setCurrentQueryIndex, setIsParent, setPreviousOrParentTNode, setSelectedIndex, ɵɵnamespaceHTML} from '../state'; +import {enterView, getBindingsEnabled, getCheckNoChangesMode, getIsParent, getLView, getNamespace, getPreviousOrParentTNode, getSelectedIndex, incrementActiveDirectiveId, isCreationMode, leaveView, resetComponentState, setActiveHostElement, setBindingRoot, setCheckNoChangesMode, setCurrentDirectiveDef, setCurrentQueryIndex, setIsParent, setPreviousOrParentTNode, setSelectedIndex, ɵɵnamespaceHTML} from '../state'; import {initializeStaticContext as initializeStaticStylingContext} from '../styling/class_and_style_bindings'; import {ANIMATION_PROP_PREFIX, isAnimationProp} from '../styling/util'; import {NO_CHANGE} from '../tokens'; @@ -101,51 +101,55 @@ export function refreshDescendantViews(lView: LView) { /** Sets the host bindings for the current view. */ export function setHostBindings(tView: TView, viewData: LView): void { - if (tView.expandoInstructions) { - let bindingRootIndex = viewData[BINDING_INDEX] = tView.expandoStartIndex; - setBindingRoot(bindingRootIndex); - let currentDirectiveIndex = -1; - let currentElementIndex = -1; - for (let i = 0; i < tView.expandoInstructions.length; i++) { - const instruction = tView.expandoInstructions[i]; - if (typeof instruction === 'number') { - if (instruction <= 0) { - // Negative numbers mean that we are starting new EXPANDO block and need to update - // the current element and directive index. - currentElementIndex = -instruction; - setActiveHostElement(currentElementIndex); + const selectedIndex = getSelectedIndex(); + try { + if (tView.expandoInstructions) { + let bindingRootIndex = viewData[BINDING_INDEX] = tView.expandoStartIndex; + setBindingRoot(bindingRootIndex); + let currentDirectiveIndex = -1; + let currentElementIndex = -1; + for (let i = 0; i < tView.expandoInstructions.length; i++) { + const instruction = tView.expandoInstructions[i]; + if (typeof instruction === 'number') { + if (instruction <= 0) { + // Negative numbers mean that we are starting new EXPANDO block and need to update + // the current element and directive index. + currentElementIndex = -instruction; + setActiveHostElement(currentElementIndex); - // Injector block and providers are taken into account. - const providerCount = (tView.expandoInstructions[++i] as number); - bindingRootIndex += INJECTOR_BLOOM_PARENT_SIZE + providerCount; + // Injector block and providers are taken into account. + const providerCount = (tView.expandoInstructions[++i] as number); + bindingRootIndex += INJECTOR_BLOOM_PARENT_SIZE + providerCount; - currentDirectiveIndex = bindingRootIndex; + currentDirectiveIndex = bindingRootIndex; + } else { + // This is either the injector size (so the binding root can skip over directives + // and get to the first set of host bindings on this node) or the host var count + // (to get to the next set of host bindings on this node). + bindingRootIndex += instruction; + } + setBindingRoot(bindingRootIndex); } else { - // This is either the injector size (so the binding root can skip over directives - // and get to the first set of host bindings on this node) or the host var count - // (to get to the next set of host bindings on this node). - bindingRootIndex += instruction; - } - setBindingRoot(bindingRootIndex); - } else { - // If it's not a number, it's a host binding function that needs to be executed. - if (instruction !== null) { - viewData[BINDING_INDEX] = bindingRootIndex; - const hostCtx = unwrapRNode(viewData[currentDirectiveIndex]); - instruction(RenderFlags.Update, hostCtx, currentElementIndex); + // If it's not a number, it's a host binding function that needs to be executed. + if (instruction !== null) { + viewData[BINDING_INDEX] = bindingRootIndex; + const hostCtx = unwrapRNode(viewData[currentDirectiveIndex]); + instruction(RenderFlags.Update, hostCtx, currentElementIndex); - // Each directive gets a uniqueId value that is the same for both - // create and update calls when the hostBindings function is called. The - // directive uniqueId is not set anywhere--it is just incremented between - // each hostBindings call and is useful for helping instruction code - // uniquely determine which directive is currently active when executed. - incrementActiveDirectiveId(); + // Each directive gets a uniqueId value that is the same for both + // create and update calls when the hostBindings function is called. The + // directive uniqueId is not set anywhere--it is just incremented between + // each hostBindings call and is useful for helping instruction code + // uniquely determine which directive is currently active when executed. + incrementActiveDirectiveId(); + } + currentDirectiveIndex++; } - currentDirectiveIndex++; } } + } finally { + setActiveHostElement(selectedIndex); } - setActiveHostElement(null); } /** Refreshes content queries for all directives in the given view. */ @@ -431,12 +435,8 @@ export function renderEmbeddedTemplate(viewToRender: LView, tView: TView, con oldView = enterView(viewToRender, viewToRender[T_HOST]); resetPreOrderHookFlags(viewToRender); - ɵɵnamespaceHTML(); + executeTemplate(tView.template !, getRenderFlags(viewToRender), context); - // Reset the selected index so we can assert that `select` was called later - setSelectedIndex(-1); - - tView.template !(getRenderFlags(viewToRender), context); // This must be set to false immediately after the first creation run because in an // ngFor loop, all the views will be created together before update mode runs and turns // off firstTemplatePass. If we don't set it here, instances will perform directive @@ -465,14 +465,7 @@ function renderComponentOrTemplate( if (creationModeIsActive) { // creation mode pass - if (templateFn) { - ɵɵnamespaceHTML(); - - // Reset the selected index so we can assert that `select` was called later - setSelectedIndex(-1); - - templateFn(RenderFlags.Create, context); - } + templateFn && executeTemplate(templateFn, RenderFlags.Create, context); refreshDescendantViews(hostView); hostView[FLAGS] &= ~LViewFlags.CreationMode; @@ -480,7 +473,7 @@ function renderComponentOrTemplate( // update mode pass resetPreOrderHookFlags(hostView); - templateFn && templateFn(RenderFlags.Update, context); + templateFn && executeTemplate(templateFn, RenderFlags.Update, context); refreshDescendantViews(hostView); } finally { if (normalExecutionPath && !creationModeIsActive && rendererFactory.end) { @@ -490,6 +483,17 @@ function renderComponentOrTemplate( } } +function executeTemplate(templateFn: ComponentTemplate, rf: RenderFlags, context: T) { + ɵɵnamespaceHTML(); + const prevSelectedIndex = getSelectedIndex(); + try { + setActiveHostElement(null); + templateFn(rf, context); + } finally { + setSelectedIndex(prevSelectedIndex); + } +} + /** * This function returns the default configuration of rendering flags depending on when the * template is in creation mode or update mode. Update block and create block are @@ -1073,26 +1077,29 @@ function invokeDirectivesHostBindings(tView: TView, viewData: LView, tNode: TNod const expando = tView.expandoInstructions !; const firstTemplatePass = tView.firstTemplatePass; const elementIndex = tNode.index - HEADER_OFFSET; - setActiveHostElement(elementIndex); + const selectedIndex = getSelectedIndex(); + try { + setActiveHostElement(elementIndex); - for (let i = start; i < end; i++) { - const def = tView.data[i] as DirectiveDef; - const directive = viewData[i]; - if (def.hostBindings) { - invokeHostBindingsInCreationMode(def, expando, directive, tNode, firstTemplatePass); + for (let i = start; i < end; i++) { + const def = tView.data[i] as DirectiveDef; + const directive = viewData[i]; + if (def.hostBindings) { + invokeHostBindingsInCreationMode(def, expando, directive, tNode, firstTemplatePass); - // Each directive gets a uniqueId value that is the same for both - // create and update calls when the hostBindings function is called. The - // directive uniqueId is not set anywhere--it is just incremented between - // each hostBindings call and is useful for helping instruction code - // uniquely determine which directive is currently active when executed. - incrementActiveDirectiveId(); - } else if (firstTemplatePass) { - expando.push(null); + // Each directive gets a uniqueId value that is the same for both + // create and update calls when the hostBindings function is called. The + // directive uniqueId is not set anywhere--it is just incremented between + // each hostBindings call and is useful for helping instruction code + // uniquely determine which directive is currently active when executed. + incrementActiveDirectiveId(); + } else if (firstTemplatePass) { + expando.push(null); + } } + } finally { + setActiveHostElement(selectedIndex); } - - setActiveHostElement(null); } export function invokeHostBindingsInCreationMode( @@ -1685,14 +1692,8 @@ export function checkView(hostView: LView, component: T) { try { resetPreOrderHookFlags(hostView); - ɵɵnamespaceHTML(); creationMode && executeViewQueryFn(RenderFlags.Create, hostTView, component); - - // Reset the selected index so we can assert that `select` was called later - setSelectedIndex(-1); - - templateFn(getRenderFlags(hostView), component); - + executeTemplate(templateFn, getRenderFlags(hostView), component); refreshDescendantViews(hostView); // Only check view queries again in creation mode if there are static view queries if (!creationMode || hostTView.staticViewQueries) { diff --git a/packages/core/src/render3/jit/environment.ts b/packages/core/src/render3/jit/environment.ts index fa0f5ad8db..2488ef1991 100644 --- a/packages/core/src/render3/jit/environment.ts +++ b/packages/core/src/render3/jit/environment.ts @@ -85,6 +85,7 @@ export const angularCoreEnv: {[name: string]: Function} = { 'ɵɵpipeBind4': r3.ɵɵpipeBind4, 'ɵɵpipeBindV': r3.ɵɵpipeBindV, 'ɵɵprojectionDef': r3.ɵɵprojectionDef, + 'ɵɵproperty': r3.ɵɵproperty, 'ɵɵpipe': r3.ɵɵpipe, 'ɵɵqueryRefresh': r3.ɵɵqueryRefresh, 'ɵɵviewQuery': r3.ɵɵviewQuery, diff --git a/packages/core/src/render3/state.ts b/packages/core/src/render3/state.ts index 8612cfa9d6..43cd0988d0 100644 --- a/packages/core/src/render3/state.ts +++ b/packages/core/src/render3/state.ts @@ -470,9 +470,6 @@ let _selectedIndex = -1; * current `LView` to act on. */ export function getSelectedIndex() { - ngDevMode && - assertGreaterThan( - _selectedIndex, -1, 'select() should be called prior to retrieving the selected index'); return _selectedIndex; } diff --git a/packages/core/test/acceptance/styling_spec.ts b/packages/core/test/acceptance/styling_spec.ts index 1644933aea..613d9c4432 100644 --- a/packages/core/test/acceptance/styling_spec.ts +++ b/packages/core/test/acceptance/styling_spec.ts @@ -5,7 +5,7 @@ * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -import {Component, Directive, ElementRef, HostBinding} from '@angular/core'; +import {Component, ComponentFactoryResolver, ComponentRef, Directive, ElementRef, HostBinding, Input, NgModule, ViewChild, ViewContainerRef} from '@angular/core'; import {TestBed} from '@angular/core/testing'; import {expect} from '@angular/platform-browser/testing/src/matchers'; import {ivyEnabled, onlyInIvy} from '@angular/private/testing'; @@ -97,6 +97,99 @@ describe('acceptance integration tests', () => { expect(element.classList.contains('baz')).toBeTruthy(); }); + it('should not cause problems if detectChanges is called when a property updates', () => { + /** + * Angular Material CDK Tree contains a code path whereby: + * + * 1. During the execution of a template function in which **more than one** property is + * updated in a row. + * 2. A property that **is not the last property** is updated in the **original template**: + * - That sets up a new observable and subscribes to it + * - The new observable it sets up can emit synchronously. + * - When it emits, it calls `detectChanges` on a `ViewRef` that it has a handle to + * - That executes a **different template**, that has host bindings + * - this executes `setHostBindings` + * - Inside of `setHostBindings` we are currently updating the selected index **global + * state** via `setActiveHostElement`. + * 3. We attempt to update the next property in the **original template**. + * - But the selected index has been altered, and we get errors. + */ + + @Component({ + selector: 'child', + template: `...`, + }) + class ChildCmp { + } + + @Component({ + selector: 'parent', + template: ` +
+
+

{{prop}}

+

{{prop2}}

+
+ `, + host: { + '[style.color]': 'color', + }, + }) + class ParentCmp { + private _prop = ''; + + @ViewChild('template', {read: ViewContainerRef}) + vcr: ViewContainerRef = null !; + + private child: ComponentRef = null !; + + @Input() + set prop(value: string) { + // Material CdkTree has at least one scenario where setting a property causes a data source + // to update, which causes a synchronous call to detectChanges(). + this._prop = value; + if (this.child) { + this.child.changeDetectorRef.detectChanges(); + } + } + + get prop() { return this._prop; } + + @Input() + prop2 = 0; + + ngAfterViewInit() { + const factory = this.componentFactoryResolver.resolveComponentFactory(ChildCmp); + this.child = this.vcr.createComponent(factory); + } + + constructor(private componentFactoryResolver: ComponentFactoryResolver) {} + } + + @Component({ + template: ``, + }) + class App { + prop = 'a'; + prop2 = 1; + } + + @NgModule({ + entryComponents: [ChildCmp], + declarations: [ChildCmp], + }) + class ChildCmpModule { + } + + TestBed.configureTestingModule({declarations: [App, ParentCmp], imports: [ChildCmpModule]}); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + fixture.componentInstance.prop = 'b'; + fixture.componentInstance.prop2 = 2; + fixture.detectChanges(); + }); + it('should render inline style and class attribute values on the element before a directive is instantiated', () => { @Component({ 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 77cd3d5d65..738ae939f7 100644 --- a/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json +++ b/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json @@ -278,6 +278,9 @@ { "name": "executePreOrderHooks" }, + { + "name": "executeTemplate" + }, { "name": "executeViewQueryFn" }, @@ -410,6 +413,9 @@ { "name": "getRootView" }, + { + "name": "getSelectedIndex" + }, { "name": "getStylingContextFromLView" }, 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 0f92586a4d..9a1ad2d784 100644 --- a/packages/core/test/bundling/hello_world/bundle.golden_symbols.json +++ b/packages/core/test/bundling/hello_world/bundle.golden_symbols.json @@ -218,6 +218,9 @@ { "name": "executePreOrderHooks" }, + { + "name": "executeTemplate" + }, { "name": "executeViewQueryFn" }, @@ -320,6 +323,9 @@ { "name": "getRootView" }, + { + "name": "getSelectedIndex" + }, { "name": "hasParentInjector" }, diff --git a/packages/core/test/bundling/todo/bundle.golden_symbols.json b/packages/core/test/bundling/todo/bundle.golden_symbols.json index 5aa942157e..6fb74a844e 100644 --- a/packages/core/test/bundling/todo/bundle.golden_symbols.json +++ b/packages/core/test/bundling/todo/bundle.golden_symbols.json @@ -593,6 +593,9 @@ { "name": "executePreOrderHooks" }, + { + "name": "executeTemplate" + }, { "name": "executeViewQueryFn" }, @@ -821,6 +824,9 @@ { "name": "getRootView" }, + { + "name": "getSelectedIndex" + }, { "name": "getSinglePropIndexValue" }, @@ -1346,9 +1352,6 @@ { "name": "ɵɵelementEnd" }, - { - "name": "ɵɵelementProperty" - }, { "name": "ɵɵelementStart" }, @@ -1376,6 +1379,9 @@ { "name": "ɵɵnextContext" }, + { + "name": "ɵɵproperty" + }, { "name": "ɵɵreference" }, diff --git a/tools/public_api_guard/core/core.d.ts b/tools/public_api_guard/core/core.d.ts index cb31ced994..ea05de210d 100644 --- a/tools/public_api_guard/core/core.d.ts +++ b/tools/public_api_guard/core/core.d.ts @@ -937,6 +937,8 @@ export declare function ɵɵprojection(nodeIndex: number, selectorIndex?: number export declare function ɵɵprojectionDef(selectors?: CssSelectorList[]): void; +export declare function ɵɵproperty(propName: string, value: T, sanitizer?: SanitizerFn | null, nativeOnly?: boolean): TsickleIssue1009; + export declare function ɵɵProvidersFeature(providers: Provider[], viewProviders?: Provider[]): (definition: DirectiveDef) => void; export declare function ɵɵpureFunction0(slotOffset: number, pureFn: () => T, thisArg?: any): T;