Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(ivy): simplify property binding metadata #32457

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
98 changes: 21 additions & 77 deletions packages/core/src/debug/debug_node.ts
Expand Up @@ -17,7 +17,7 @@ import {stylingMapToStringMap} from '../render3/styling_next/map_based_bindings'
import {NodeStylingDebug} from '../render3/styling_next/styling_debug';
import {isStylingContext} from '../render3/styling_next/util';
import {getComponent, getContext, getInjectionTokens, getInjector, getListeners, getLocalRefs, isBrowserEvents, loadLContext} from '../render3/util/discovery_utils';
import {INTERPOLATION_DELIMITER, isPropMetadataString, renderStringify} from '../render3/util/misc_utils';
import {INTERPOLATION_DELIMITER, renderStringify} from '../render3/util/misc_utils';
import {findComponentView} from '../render3/util/view_traversal_utils';
import {getComponentViewByIndex, getNativeByTNodeOrNull} from '../render3/util/view_utils';
import {assertDomNode} from '../util/assert';
Expand Down Expand Up @@ -282,15 +282,14 @@ class DebugElement__POST_R3__ extends DebugNode__POST_R3__ implements DebugEleme
const tNode = tData[context.nodeIndex] as TNode;

const properties = collectPropertyBindings(tNode, lView, tData);
const hostProperties = collectHostPropertyBindings(tNode, lView, tData);
const className = collectClassNames(this);
const output = {...properties, ...hostProperties};

if (className) {
output['className'] = output['className'] ? output['className'] + ` ${className}` : className;
properties['className'] =
properties['className'] ? properties['className'] + ` ${className}` : className;
}

return output;
return properties;
}

get attributes(): {[key: string]: string | null;} {
Expand Down Expand Up @@ -645,81 +644,26 @@ function _queryNativeNodeDescendants(
function collectPropertyBindings(
tNode: TNode, lView: LView, tData: TData): {[key: string]: string} {
const properties: {[key: string]: string} = {};
let bindingIndex = getFirstBindingIndex(tNode.propertyMetadataStartIndex, tData);

while (bindingIndex < tNode.propertyMetadataEndIndex) {
let value: any;
let propMetadata = tData[bindingIndex] as string;
while (!isPropMetadataString(propMetadata)) {
// This is the first value for an interpolation. We need to build up
// the full interpolation by combining runtime values in LView with
// the static interstitial values stored in TData.
value = (value || '') + renderStringify(lView[bindingIndex]) + tData[bindingIndex];
propMetadata = tData[++bindingIndex] as string;
}
value = value === undefined ? lView[bindingIndex] : value += lView[bindingIndex];
// Property metadata string has 3 parts: property name, prefix, and suffix
const metadataParts = propMetadata.split(INTERPOLATION_DELIMITER);
const propertyName = metadataParts[0];
// Attr bindings don't have property names and should be skipped
if (propertyName) {
// Wrap value with prefix and suffix (will be '' for normal bindings), if they're defined.
// Avoid wrapping for normal bindings so that the value doesn't get cast to a string.
properties[propertyName] = (metadataParts[1] && metadataParts[2]) ?
metadataParts[1] + value + metadataParts[2] :
value;
let bindingIndexes = tNode.propertyBindings;

if (bindingIndexes !== null) {
for (let i = 0; i < bindingIndexes.length; i++) {
const bindingIndex = bindingIndexes[i];
const propMetadata = tData[bindingIndex] as string;
const metadataParts = propMetadata.split(INTERPOLATION_DELIMITER);
const propertyName = metadataParts[0];
if (metadataParts.length > 1) {
let value = metadataParts[1];
for (let j = 1; j < metadataParts.length - 1; j++) {
value += renderStringify(lView[bindingIndex + j - 1]) + metadataParts[j + 1];
}
properties[propertyName] = value;
} else {
properties[propertyName] = lView[bindingIndex];
}
}
bindingIndex++;
}
return properties;
}

/**
* Retrieves the first binding index that holds values for this property
* binding.
*
* For normal bindings (e.g. `[id]="id"`), the binding index is the
* same as the metadata index. For interpolations (e.g. `id="{{id}}-{{name}}"`),
* there can be multiple binding values, so we might have to loop backwards
* from the metadata index until we find the first one.
*
* @param metadataIndex The index of the first property metadata string for
* this node.
* @param tData The data array for the current TView
* @returns The first binding index for this binding
*/
function getFirstBindingIndex(metadataIndex: number, tData: TData): number {
let currentBindingIndex = metadataIndex - 1;

// If the slot before the metadata holds a string, we know that this
// metadata applies to an interpolation with at least 2 bindings, and
// we need to search further to access the first binding value.
let currentValue = tData[currentBindingIndex];

// We need to iterate until we hit either a:
// - TNode (it is an element slot marking the end of `consts` section), OR a
// - metadata string (slot is attribute metadata or a previous node's property metadata)
while (typeof currentValue === 'string' && !isPropMetadataString(currentValue)) {
currentValue = tData[--currentBindingIndex];
}
return currentBindingIndex + 1;
}

function collectHostPropertyBindings(
tNode: TNode, lView: LView, tData: TData): {[key: string]: string} {
const properties: {[key: string]: string} = {};

// Host binding values for a node are stored after directives on that node
let hostPropIndex = tNode.directiveEnd;
let propMetadata = tData[hostPropIndex] as any;

// When we reach a value in TView.data that is not a string, we know we've
// hit the next node's providers and directives and should stop copying data.
while (typeof propMetadata === 'string') {
const propertyName = propMetadata.split(INTERPOLATION_DELIMITER)[0];
properties[propertyName] = lView[hostPropIndex];
propMetadata = tData[++hostPropIndex];
}
return properties;
}

Expand Down
27 changes: 14 additions & 13 deletions packages/core/src/render3/instructions/host_property.ts
Expand Up @@ -5,12 +5,13 @@
* 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, TVIEW} from '../interfaces/view';
import {getLView, getSelectedIndex} from '../state';
import {NO_CHANGE} from '../tokens';
import {bind} from './property';
import {TsickleIssue1009, elementPropertyInternal, loadComponentRenderer} from './shared';

import {TsickleIssue1009, elementPropertyInternal, loadComponentRenderer, storePropertyBindingMetadata} from './shared';

/**
* Update a property on a host element. Only applies to native node properties, not inputs.
Expand All @@ -28,12 +29,12 @@ import {TsickleIssue1009, elementPropertyInternal, loadComponentRenderer} from '
*/
export function ɵɵhostProperty<T>(
propName: string, value: T, sanitizer?: SanitizerFn | null): TsickleIssue1009 {
const index = getSelectedIndex();
ngDevMode && assertNotEqual(index, -1, 'selected index cannot be -1');
const lView = getLView();
const bindReconciledValue = bind(lView, value);
if (bindReconciledValue !== NO_CHANGE) {
elementPropertyInternal(index, propName, bindReconciledValue, sanitizer, true);
const bindingIndex = lView[BINDING_INDEX]++;
if (bindingUpdated(lView, bindingIndex, value)) {
const nodeIndex = getSelectedIndex();
elementPropertyInternal(nodeIndex, propName, value, sanitizer, true);
ngDevMode && storePropertyBindingMetadata(lView[TVIEW].data, nodeIndex, propName, bindingIndex);
}
return ɵɵhostProperty;
}
Expand Down Expand Up @@ -62,12 +63,12 @@ export function ɵɵhostProperty<T>(
*/
export function ɵɵupdateSyntheticHostBinding<T>(
propName: string, value: T | NO_CHANGE, sanitizer?: SanitizerFn | null): TsickleIssue1009 {
const index = getSelectedIndex();
const lView = getLView();
// TODO(benlesh): remove bind call here.
const bound = bind(lView, value);
if (bound !== NO_CHANGE) {
elementPropertyInternal(index, propName, bound, sanitizer, true, loadComponentRenderer);
const bindingIndex = lView[BINDING_INDEX]++;
if (bindingUpdated(lView, bindingIndex, value)) {
const nodeIndex = getSelectedIndex();
elementPropertyInternal(nodeIndex, propName, value, sanitizer, true, loadComponentRenderer);
ngDevMode && storePropertyBindingMetadata(lView[TVIEW].data, nodeIndex, propName, bindingIndex);
}
return ɵɵupdateSyntheticHostBinding;
}
97 changes: 1 addition & 96 deletions packages/core/src/render3/instructions/interpolation.ts
Expand Up @@ -8,10 +8,9 @@

import {assertEqual, assertLessThan} from '../../util/assert';
import {bindingUpdated, bindingUpdated2, bindingUpdated3, bindingUpdated4} from '../bindings';
import {BINDING_INDEX, LView, TVIEW} from '../interfaces/view';
import {BINDING_INDEX, LView} from '../interfaces/view';
import {NO_CHANGE} from '../tokens';
import {renderStringify} from '../util/misc_utils';
import {storeBindingMetadata} from './shared';



Expand All @@ -31,23 +30,13 @@ export function interpolationV(lView: LView, values: any[]): string|NO_CHANGE {
ngDevMode && assertLessThan(2, values.length, 'should have at least 3 values');
ngDevMode && assertEqual(values.length % 2, 1, 'should have an odd number of values');
let isBindingUpdated = false;
const tData = lView[TVIEW].data;
let bindingIndex = lView[BINDING_INDEX];

if (tData[bindingIndex] == null) {
// 2 is the index of the first static interstitial value (ie. not prefix)
for (let i = 2; i < values.length; i += 2) {
tData[bindingIndex++] = values[i];
}
bindingIndex = lView[BINDING_INDEX];
}

for (let i = 1; i < values.length; i += 2) {
// Check if bindings (odd indexes) have changed
isBindingUpdated = bindingUpdated(lView, bindingIndex++, values[i]) || isBindingUpdated;
}
lView[BINDING_INDEX] = bindingIndex;
storeBindingMetadata(lView, values[0], values[values.length - 1]);

if (!isBindingUpdated) {
return NO_CHANGE;
Expand All @@ -72,7 +61,6 @@ export function interpolationV(lView: LView, values: any[]): string|NO_CHANGE {
export function interpolation1(lView: LView, prefix: string, v0: any, suffix: string): string|
NO_CHANGE {
const different = bindingUpdated(lView, lView[BINDING_INDEX]++, v0);
ngDevMode && storeBindingMetadata(lView, prefix, suffix);
return different ? prefix + renderStringify(v0) + suffix : NO_CHANGE;
}

Expand All @@ -85,14 +73,6 @@ export function interpolation2(
const different = bindingUpdated2(lView, bindingIndex, v0, v1);
lView[BINDING_INDEX] += 2;

if (ngDevMode) {
// Only set static strings the first time (data will be null subsequent runs).
const data = storeBindingMetadata(lView, prefix, suffix);
if (data) {
lView[TVIEW].data[bindingIndex] = i0;
}
}

return different ? prefix + renderStringify(v0) + i0 + renderStringify(v1) + suffix : NO_CHANGE;
}

Expand All @@ -106,16 +86,6 @@ export function interpolation3(
const different = bindingUpdated3(lView, bindingIndex, v0, v1, v2);
lView[BINDING_INDEX] += 3;

if (ngDevMode) {
// Only set static strings the first time (data will be null subsequent runs).
const data = storeBindingMetadata(lView, prefix, suffix);
if (data) {
const tData = lView[TVIEW].data;
tData[bindingIndex] = i0;
tData[bindingIndex + 1] = i1;
}
}

return different ?
prefix + renderStringify(v0) + i0 + renderStringify(v1) + i1 + renderStringify(v2) + suffix :
NO_CHANGE;
Expand All @@ -131,17 +101,6 @@ export function interpolation4(
const different = bindingUpdated4(lView, bindingIndex, v0, v1, v2, v3);
lView[BINDING_INDEX] += 4;

if (ngDevMode) {
// Only set static strings the first time (data will be null subsequent runs).
const data = storeBindingMetadata(lView, prefix, suffix);
if (data) {
const tData = lView[TVIEW].data;
tData[bindingIndex] = i0;
tData[bindingIndex + 1] = i1;
tData[bindingIndex + 2] = i2;
}
}

return different ?
prefix + renderStringify(v0) + i0 + renderStringify(v1) + i1 + renderStringify(v2) + i2 +
renderStringify(v3) + suffix :
Expand All @@ -159,18 +118,6 @@ export function interpolation5(
different = bindingUpdated(lView, bindingIndex + 4, v4) || different;
lView[BINDING_INDEX] += 5;

if (ngDevMode) {
// Only set static strings the first time (data will be null subsequent runs).
const data = storeBindingMetadata(lView, prefix, suffix);
if (data) {
const tData = lView[TVIEW].data;
tData[bindingIndex] = i0;
tData[bindingIndex + 1] = i1;
tData[bindingIndex + 2] = i2;
tData[bindingIndex + 3] = i3;
}
}

return different ?
prefix + renderStringify(v0) + i0 + renderStringify(v1) + i1 + renderStringify(v2) + i2 +
renderStringify(v3) + i3 + renderStringify(v4) + suffix :
Expand All @@ -188,19 +135,6 @@ export function interpolation6(
different = bindingUpdated2(lView, bindingIndex + 4, v4, v5) || different;
lView[BINDING_INDEX] += 6;

if (ngDevMode) {
// Only set static strings the first time (data will be null subsequent runs).
const data = storeBindingMetadata(lView, prefix, suffix);
if (data) {
const tData = lView[TVIEW].data;
tData[bindingIndex] = i0;
tData[bindingIndex + 1] = i1;
tData[bindingIndex + 2] = i2;
tData[bindingIndex + 3] = i3;
tData[bindingIndex + 4] = i4;
}
}

return different ?
prefix + renderStringify(v0) + i0 + renderStringify(v1) + i1 + renderStringify(v2) + i2 +
renderStringify(v3) + i3 + renderStringify(v4) + i4 + renderStringify(v5) + suffix :
Expand All @@ -219,20 +153,6 @@ export function interpolation7(
different = bindingUpdated3(lView, bindingIndex + 4, v4, v5, v6) || different;
lView[BINDING_INDEX] += 7;

if (ngDevMode) {
// Only set static strings the first time (data will be null subsequent runs).
const data = storeBindingMetadata(lView, prefix, suffix);
if (data) {
const tData = lView[TVIEW].data;
tData[bindingIndex] = i0;
tData[bindingIndex + 1] = i1;
tData[bindingIndex + 2] = i2;
tData[bindingIndex + 3] = i3;
tData[bindingIndex + 4] = i4;
tData[bindingIndex + 5] = i5;
}
}

return different ?
prefix + renderStringify(v0) + i0 + renderStringify(v1) + i1 + renderStringify(v2) + i2 +
renderStringify(v3) + i3 + renderStringify(v4) + i4 + renderStringify(v5) + i5 +
Expand All @@ -252,21 +172,6 @@ export function interpolation8(
different = bindingUpdated4(lView, bindingIndex + 4, v4, v5, v6, v7) || different;
lView[BINDING_INDEX] += 8;

if (ngDevMode) {
// Only set static strings the first time (data will be null subsequent runs).
const data = storeBindingMetadata(lView, prefix, suffix);
if (data) {
const tData = lView[TVIEW].data;
tData[bindingIndex] = i0;
tData[bindingIndex + 1] = i1;
tData[bindingIndex + 2] = i2;
tData[bindingIndex + 3] = i3;
tData[bindingIndex + 4] = i4;
tData[bindingIndex + 5] = i5;
tData[bindingIndex + 6] = i6;
}
}

return different ?
prefix + renderStringify(v0) + i0 + renderStringify(v1) + i1 + renderStringify(v2) + i2 +
renderStringify(v3) + i3 + renderStringify(v4) + i4 + renderStringify(v5) + i5 +
Expand Down
3 changes: 1 addition & 2 deletions packages/core/src/render3/instructions/lview_debug.ts
Expand Up @@ -118,8 +118,7 @@ export const TNodeConstructor = class TNode implements ITNode {
public injectorIndex: number, //
public directiveStart: number, //
public directiveEnd: number, //
public propertyMetadataStartIndex: number, //
public propertyMetadataEndIndex: number, //
public propertyBindings: number[]|null, //
public flags: TNodeFlags, //
public providerIndexes: TNodeProviderIndexes, //
public tagName: string|null, //
Expand Down