Skip to content

Commit 831e71e

Browse files
karajasonaden
authored andcommitted
fix(ivy): host bindings should support array/object literals (angular#25583)
PR Close angular#25583
1 parent fc89479 commit 831e71e

File tree

11 files changed

+446
-77
lines changed

11 files changed

+446
-77
lines changed

packages/core/src/render3/component.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,6 @@ export function renderComponent<T>(
112112
rootView[INJECTOR] = opts.injector || null;
113113

114114
const oldView = enterView(rootView, null !);
115-
rootView[BINDING_INDEX] = rootView[TVIEW].bindingStartIndex;
116115
let elementNode: LElementNode;
117116
let component: T;
118117
try {

packages/core/src/render3/component_ref.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,6 @@ export class ComponentFactory<T> extends viewEngine_ComponentFactory<T> {
127127

128128
// rootView is the parent when bootstrapping
129129
const oldView = enterView(rootView, null !);
130-
rootView[BINDING_INDEX] = rootView[TVIEW].bindingStartIndex;
131130

132131
let component: T;
133132
let elementNode: LElementNode;

packages/core/src/render3/definition.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ export function defineComponent<T>(componentDefinition: {
5656
/**
5757
* The number of nodes, local refs, and pipes in this component template.
5858
*
59-
* Used to calculate the length of the component's LViewData array, so we
59+
* Used to calculate the length of this component's LViewData array, so we
6060
* can pre-fill the array and set the binding start index.
6161
*/
6262
// TODO(kara): remove queries from this count
@@ -65,11 +65,19 @@ export function defineComponent<T>(componentDefinition: {
6565
/**
6666
* The number of bindings in this component template (including pure fn bindings).
6767
*
68-
* Used to calculate the length of the component's LViewData array, so we
68+
* Used to calculate the length of this component's LViewData array, so we
6969
* can pre-fill the array and set the host binding start index.
7070
*/
7171
vars: number;
7272

73+
/**
74+
* The number of host bindings (including pure fn bindings) in this component.
75+
*
76+
* Used to calculate the length of the LViewData array for the *parent* component
77+
* of this component.
78+
*/
79+
hostVars?: number;
80+
7381
/**
7482
* Static attributes to set on host element.
7583
*
@@ -264,6 +272,7 @@ export function defineComponent<T>(componentDefinition: {
264272
diPublic: null,
265273
consts: componentDefinition.consts,
266274
vars: componentDefinition.vars,
275+
hostVars: componentDefinition.hostVars || 0,
267276
factory: componentDefinition.factory,
268277
template: componentDefinition.template || null !,
269278
hostBindings: componentDefinition.hostBindings || null,
@@ -577,6 +586,14 @@ export const defineDirective = defineComponent as any as<T>(directiveDefinition:
577586
*/
578587
features?: DirectiveDefFeature[];
579588

589+
/**
590+
* The number of host bindings (including pure fn bindings) in this directive.
591+
*
592+
* Used to calculate the length of the LViewData array for the *parent* component
593+
* of this directive.
594+
*/
595+
hostVars?: number;
596+
580597
/**
581598
* Function executed by the parent template to allow child directive to apply host bindings.
582599
*/

packages/core/src/render3/instructions.ts

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,18 @@ let checkNoChangesMode = false;
239239
/** Whether or not this is the first time the current view has been processed. */
240240
let firstTemplatePass = true;
241241

242+
/**
243+
* The root index from which pure function instructions should calculate their binding
244+
* indices. In component views, this is TView.bindingStartIndex. In a host binding
245+
* context, this is the TView.hostBindingStartIndex + any hostVars before the given dir.
246+
*/
247+
let bindingRootIndex: number = -1;
248+
249+
// top level variables should not be exported for performance reasons (PERF_NOTES.md)
250+
export function getBindingRoot() {
251+
return bindingRootIndex;
252+
}
253+
242254
const enum BindingDirection {
243255
Input,
244256
Output,
@@ -263,7 +275,7 @@ export function enterView(newView: LViewData, host: LElementNode | LViewNode | n
263275

264276
creationMode = newView && (newView[FLAGS] & LViewFlags.CreationMode) === LViewFlags.CreationMode;
265277
firstTemplatePass = newView && tView.firstTemplatePass;
266-
278+
bindingRootIndex = newView && tView.bindingStartIndex;
267279
renderer = newView && newView[RENDERER];
268280

269281
if (host != null) {
@@ -295,7 +307,7 @@ export function leaveView(newView: LViewData, creationOnly?: boolean): void {
295307
viewData[FLAGS] &= ~(LViewFlags.CreationMode | LViewFlags.Dirty);
296308
}
297309
viewData[FLAGS] |= LViewFlags.RunInit;
298-
viewData[BINDING_INDEX] = -1;
310+
viewData[BINDING_INDEX] = tView.bindingStartIndex;
299311
enterView(newView, null);
300312
}
301313

@@ -329,11 +341,13 @@ function refreshDescendantViews() {
329341
/** Sets the host bindings for the current view. */
330342
export function setHostBindings(bindings: number[] | null): void {
331343
if (bindings != null) {
344+
bindingRootIndex = viewData[BINDING_INDEX] = tView.hostBindingStartIndex;
332345
const defs = tView.directives !;
333346
for (let i = 0; i < bindings.length; i += 2) {
334347
const dirIndex = bindings[i];
335348
const def = defs[dirIndex] as DirectiveDefInternal<any>;
336349
def.hostBindings && def.hostBindings(dirIndex, bindings[i + 1]);
350+
bindingRootIndex = viewData[BINDING_INDEX] = bindingRootIndex + def.hostVars;
337351
}
338352
}
339353
}
@@ -377,7 +391,7 @@ export function createLViewData<T>(
377391
null, // queries
378392
flags | LViewFlags.CreationMode | LViewFlags.Attached | LViewFlags.RunInit, // flags
379393
null !, // hostNode
380-
-1, // bindingIndex
394+
tView.bindingStartIndex, // bindingIndex
381395
null, // directives
382396
null, // cleanupInstances
383397
context, // context
@@ -600,7 +614,6 @@ export function renderEmbeddedTemplate<T>(
600614

601615
oldView = enterView(viewNode.data !, viewNode);
602616
namespaceHTML();
603-
viewData[BINDING_INDEX] = tView.bindingStartIndex;
604617
tView.template !(rf, context);
605618
if (rf & RenderFlags.Update) {
606619
refreshDescendantViews();
@@ -644,7 +657,6 @@ export function renderComponentOrTemplate<T>(
644657
}
645658
if (templateFn) {
646659
namespaceHTML();
647-
viewData[BINDING_INDEX] = tView.bindingStartIndex;
648660
templateFn(getRenderFlags(hostView), componentOrContext !);
649661
refreshDescendantViews();
650662
} else {
@@ -1042,14 +1054,16 @@ export function createTView(
10421054
directives: DirectiveDefListOrFactory | null, pipes: PipeDefListOrFactory | null,
10431055
viewQuery: ComponentQuery<any>| null): TView {
10441056
ngDevMode && ngDevMode.tView++;
1057+
const bindingStartIndex = HEADER_OFFSET + consts;
10451058
return {
10461059
id: viewIndex,
10471060
template: templateFn,
10481061
viewQuery: viewQuery,
10491062
node: null !,
10501063
data: HEADER_FILLER.slice(), // Fill in to match HEADER_OFFSET in LViewData
10511064
childIndex: -1, // Children set in addToViewTree(), if any
1052-
bindingStartIndex: HEADER_OFFSET + consts,
1065+
bindingStartIndex: bindingStartIndex,
1066+
hostBindingStartIndex: bindingStartIndex + vars,
10531067
directives: null,
10541068
firstTemplatePass: true,
10551069
initHooks: null,
@@ -2062,7 +2076,6 @@ export function embeddedViewStart(viewBlockId: number, consts: number, vars: num
20622076
}
20632077
lContainer[ACTIVE_INDEX] !++;
20642078
}
2065-
viewData[BINDING_INDEX] = tView.bindingStartIndex;
20662079
return getRenderFlags(viewNode.data);
20672080
}
20682081

@@ -2467,7 +2480,6 @@ export function detectChangesInternal<T>(
24672480
const hostTView = hostView[TVIEW];
24682481
const templateFn = hostTView.template !;
24692482
const viewQuery = hostTView.viewQuery;
2470-
viewData[BINDING_INDEX] = tView.bindingStartIndex;
24712483

24722484
try {
24732485
namespaceHTML();

packages/core/src/render3/interfaces/definition.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,14 @@ export interface DirectiveDef<T, Selector extends string> extends BaseDef<T> {
141141
/** Refreshes content queries associated with directives in a given view */
142142
contentQueriesRefresh: ((directiveIndex: number, queryIndex: number) => void)|null;
143143

144+
/**
145+
* The number of host bindings (including pure fn bindings) in this directive/component.
146+
*
147+
* Used to calculate the length of the LViewData array for the *parent* component
148+
* of this directive/component.
149+
*/
150+
hostVars: number;
151+
144152
/** Refreshes host bindings on the associated directive. */
145153
hostBindings: ((directiveIndex: number, elementIndex: number) => void)|null;
146154

packages/core/src/render3/interfaces/view.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,13 @@ export interface TView {
289289
*/
290290
bindingStartIndex: number;
291291

292+
/**
293+
* The index at which the data array begins to store host bindings for components
294+
* or directives in its template. Saving this value ensures that we can set the
295+
* binding root and binding index correctly before checking host bindings.
296+
*/
297+
hostBindingStartIndex: number;
298+
292299
/**
293300
* Index of the host node of the first LView or LContainer beneath this LView in
294301
* the hierarchy.

packages/core/src/render3/pure_function.ts

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,22 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import {bindingUpdated, bindingUpdated2, bindingUpdated4, updateBinding, getBinding, getCreationMode, getTView, bindingUpdated3,} from './instructions';
9+
import {bindingUpdated, bindingUpdated2, bindingUpdated4, updateBinding, getBinding, getCreationMode, bindingUpdated3, getBindingRoot, getTView,} from './instructions';
1010

1111
/**
1212
* Bindings for pure functions are stored after regular bindings.
1313
*
14-
* ----------------------------------------------------------------------------
15-
* | LNodes / local refs / pipes ... | regular bindings / interpolations | pure function bindings
16-
* ----------------------------------------------------------------------------
17-
* ^
18-
* TView.bindingStartIndex
14+
* |--------consts--------|----------------vars----------------|------ hostVars (dir1) ------|
15+
* ---------------------------------------------------------------------------------------------
16+
* | nodes / refs / pipes | bindings | pure function bindings | host bindings | host slots |
17+
* ---------------------------------------------------------------------------------------------
18+
* ^ ^
19+
* TView.bindingStartIndex TView.hostBindingStartIndex
1920
*
20-
* Pure function instructions are given an offset from TView.bindingStartIndex.
21-
* Adding the offset to TView.bindingStartIndex gives the first index where the bindings
22-
* are stored.
21+
* Pure function instructions are given an offset from the binding root. Adding the offset to the
22+
* binding root gives the first index where the bindings are stored. In component views, the binding
23+
* root is the bindingStartIndex. In host bindings, the binding root is the hostBindingStartIndex +
24+
* any hostVars in directives evaluated before it.
2325
*/
2426

2527
/**
@@ -33,7 +35,7 @@ import {bindingUpdated, bindingUpdated2, bindingUpdated4, updateBinding, getBind
3335
*/
3436
export function pureFunction0<T>(slotOffset: number, pureFn: () => T, thisArg?: any): T {
3537
// TODO(kara): use bindingRoot instead of bindingStartIndex when implementing host bindings
36-
const bindingIndex = getTView().bindingStartIndex + slotOffset;
38+
const bindingIndex = getBindingRoot() + slotOffset;
3739
return getCreationMode() ?
3840
updateBinding(bindingIndex, thisArg ? pureFn.call(thisArg) : pureFn()) :
3941
getBinding(bindingIndex);
@@ -52,7 +54,7 @@ export function pureFunction0<T>(slotOffset: number, pureFn: () => T, thisArg?:
5254
export function pureFunction1(
5355
slotOffset: number, pureFn: (v: any) => any, exp: any, thisArg?: any): any {
5456
// TODO(kara): use bindingRoot instead of bindingStartIndex when implementing host bindings
55-
const bindingIndex = getTView().bindingStartIndex + slotOffset;
57+
const bindingIndex = getBindingRoot() + slotOffset;
5658
return bindingUpdated(bindingIndex, exp) ?
5759
updateBinding(bindingIndex + 1, thisArg ? pureFn.call(thisArg, exp) : pureFn(exp)) :
5860
getBinding(bindingIndex + 1);
@@ -73,7 +75,7 @@ export function pureFunction2(
7375
slotOffset: number, pureFn: (v1: any, v2: any) => any, exp1: any, exp2: any,
7476
thisArg?: any): any {
7577
// TODO(kara): use bindingRoot instead of bindingStartIndex when implementing host bindings
76-
const bindingIndex = getTView().bindingStartIndex + slotOffset;
78+
const bindingIndex = getBindingRoot() + slotOffset;
7779
return bindingUpdated2(bindingIndex, exp1, exp2) ?
7880
updateBinding(
7981
bindingIndex + 2, thisArg ? pureFn.call(thisArg, exp1, exp2) : pureFn(exp1, exp2)) :
@@ -96,7 +98,7 @@ export function pureFunction3(
9698
slotOffset: number, pureFn: (v1: any, v2: any, v3: any) => any, exp1: any, exp2: any, exp3: any,
9799
thisArg?: any): any {
98100
// TODO(kara): use bindingRoot instead of bindingStartIndex when implementing host bindings
99-
const bindingIndex = getTView().bindingStartIndex + slotOffset;
101+
const bindingIndex = getBindingRoot() + slotOffset;
100102
return bindingUpdated3(bindingIndex, exp1, exp2, exp3) ?
101103
updateBinding(
102104
bindingIndex + 3,
@@ -121,7 +123,7 @@ export function pureFunction4(
121123
slotOffset: number, pureFn: (v1: any, v2: any, v3: any, v4: any) => any, exp1: any, exp2: any,
122124
exp3: any, exp4: any, thisArg?: any): any {
123125
// TODO(kara): use bindingRoot instead of bindingStartIndex when implementing host bindings
124-
const bindingIndex = getTView().bindingStartIndex + slotOffset;
126+
const bindingIndex = getBindingRoot() + slotOffset;
125127
return bindingUpdated4(bindingIndex, exp1, exp2, exp3, exp4) ?
126128
updateBinding(
127129
bindingIndex + 4,
@@ -147,7 +149,7 @@ export function pureFunction5(
147149
slotOffset: number, pureFn: (v1: any, v2: any, v3: any, v4: any, v5: any) => any, exp1: any,
148150
exp2: any, exp3: any, exp4: any, exp5: any, thisArg?: any): any {
149151
// TODO(kara): use bindingRoot instead of bindingStartIndex when implementing host bindings
150-
const bindingIndex = getTView().bindingStartIndex + slotOffset;
152+
const bindingIndex = getBindingRoot() + slotOffset;
151153
const different = bindingUpdated4(bindingIndex, exp1, exp2, exp3, exp4);
152154
return bindingUpdated(bindingIndex + 4, exp5) || different ?
153155
updateBinding(
@@ -175,7 +177,7 @@ export function pureFunction6(
175177
slotOffset: number, pureFn: (v1: any, v2: any, v3: any, v4: any, v5: any, v6: any) => any,
176178
exp1: any, exp2: any, exp3: any, exp4: any, exp5: any, exp6: any, thisArg?: any): any {
177179
// TODO(kara): use bindingRoot instead of bindingStartIndex when implementing host bindings
178-
const bindingIndex = getTView().bindingStartIndex + slotOffset;
180+
const bindingIndex = getBindingRoot() + slotOffset;
179181
const different = bindingUpdated4(bindingIndex, exp1, exp2, exp3, exp4);
180182
return bindingUpdated2(bindingIndex + 4, exp5, exp6) || different ?
181183
updateBinding(
@@ -205,7 +207,7 @@ export function pureFunction7(
205207
pureFn: (v1: any, v2: any, v3: any, v4: any, v5: any, v6: any, v7: any) => any, exp1: any,
206208
exp2: any, exp3: any, exp4: any, exp5: any, exp6: any, exp7: any, thisArg?: any): any {
207209
// TODO(kara): use bindingRoot instead of bindingStartIndex when implementing host bindings
208-
const bindingIndex = getTView().bindingStartIndex + slotOffset;
210+
const bindingIndex = getBindingRoot() + slotOffset;
209211
let different = bindingUpdated4(bindingIndex, exp1, exp2, exp3, exp4);
210212
return bindingUpdated3(bindingIndex + 4, exp5, exp6, exp7) || different ?
211213
updateBinding(
@@ -238,7 +240,7 @@ export function pureFunction8(
238240
exp1: any, exp2: any, exp3: any, exp4: any, exp5: any, exp6: any, exp7: any, exp8: any,
239241
thisArg?: any): any {
240242
// TODO(kara): use bindingRoot instead of bindingStartIndex when implementing host bindings
241-
const bindingIndex = getTView().bindingStartIndex + slotOffset;
243+
const bindingIndex = getBindingRoot() + slotOffset;
242244
const different = bindingUpdated4(bindingIndex, exp1, exp2, exp3, exp4);
243245
return bindingUpdated4(bindingIndex + 4, exp5, exp6, exp7, exp8) || different ?
244246
updateBinding(
@@ -264,7 +266,7 @@ export function pureFunction8(
264266
export function pureFunctionV(
265267
slotOffset: number, pureFn: (...v: any[]) => any, exps: any[], thisArg?: any): any {
266268
// TODO(kara): use bindingRoot instead of bindingStartIndex when implementing host bindings
267-
let bindingIndex = getTView().bindingStartIndex + slotOffset;
269+
let bindingIndex = getBindingRoot() + slotOffset;
268270
let different = false;
269271
for (let i = 0; i < exps.length; i++) {
270272
bindingUpdated(bindingIndex++, exps[i]) && (different = true);

packages/core/test/bundling/hello_world/bundle.golden_symbols.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,9 @@
9898
{
9999
"name": "baseDirectiveCreate"
100100
},
101+
{
102+
"name": "bindingRootIndex"
103+
},
101104
{
102105
"name": "bloomAdd"
103106
},

packages/core/test/bundling/todo/bundle.golden_symbols.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,9 @@
326326
{
327327
"name": "bind"
328328
},
329+
{
330+
"name": "bindingRootIndex"
331+
},
329332
{
330333
"name": "bindingUpdated"
331334
},

packages/core/test/render3/directive_spec.ts

Lines changed: 0 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -15,36 +15,6 @@ import {TemplateFixture} from './render_util';
1515

1616
describe('directive', () => {
1717

18-
describe('host', () => {
19-
20-
it('should support host bindings in directives', () => {
21-
let directiveInstance: Directive|undefined;
22-
23-
class Directive {
24-
klass = 'foo';
25-
static ngDirectiveDef = defineDirective({
26-
type: Directive,
27-
selectors: [['', 'dir', '']],
28-
factory: () => directiveInstance = new Directive,
29-
hostBindings: (directiveIndex: number, elementIndex: number) => {
30-
elementProperty(
31-
elementIndex, 'className', bind(loadDirective<Directive>(directiveIndex).klass));
32-
}
33-
});
34-
}
35-
36-
function Template() { element(0, 'span', [AttributeMarker.SelectOnly, 'dir']); }
37-
38-
const fixture = new TemplateFixture(Template, () => {}, 1, 0, [Directive]);
39-
expect(fixture.html).toEqual('<span class="foo"></span>');
40-
41-
directiveInstance !.klass = 'bar';
42-
fixture.update();
43-
expect(fixture.html).toEqual('<span class="bar"></span>');
44-
});
45-
46-
});
47-
4818
describe('selectors', () => {
4919

5020
it('should match directives with attribute selectors on bindings', () => {

0 commit comments

Comments
 (0)