diff --git a/packages/core/src/debug/debug_node.ts b/packages/core/src/debug/debug_node.ts index c204f8ef706ea..e724b960b6f85 100644 --- a/packages/core/src/debug/debug_node.ts +++ b/packages/core/src/debug/debug_node.ts @@ -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'; @@ -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;} { @@ -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; } diff --git a/packages/core/src/render3/instructions/host_property.ts b/packages/core/src/render3/instructions/host_property.ts index d89a97e80d233..e2022637afd54 100644 --- a/packages/core/src/render3/instructions/host_property.ts +++ b/packages/core/src/render3/instructions/host_property.ts @@ -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. @@ -28,12 +29,12 @@ import {TsickleIssue1009, elementPropertyInternal, loadComponentRenderer} from ' */ export function ɵɵhostProperty( 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; } @@ -62,12 +63,12 @@ export function ɵɵhostProperty( */ export function ɵɵupdateSyntheticHostBinding( 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; } diff --git a/packages/core/src/render3/instructions/interpolation.ts b/packages/core/src/render3/instructions/interpolation.ts index 2dc3c12e53cf9..8e57f414d0736 100644 --- a/packages/core/src/render3/instructions/interpolation.ts +++ b/packages/core/src/render3/instructions/interpolation.ts @@ -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'; @@ -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; @@ -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; } @@ -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; } @@ -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; @@ -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 : @@ -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 : @@ -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 : @@ -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 + @@ -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 + diff --git a/packages/core/src/render3/instructions/lview_debug.ts b/packages/core/src/render3/instructions/lview_debug.ts index 47feb1c3633cd..9235f7e61e41c 100644 --- a/packages/core/src/render3/instructions/lview_debug.ts +++ b/packages/core/src/render3/instructions/lview_debug.ts @@ -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, // diff --git a/packages/core/src/render3/instructions/property.ts b/packages/core/src/render3/instructions/property.ts index a86864a4f79b0..0ae3e0537e837 100644 --- a/packages/core/src/render3/instructions/property.ts +++ b/packages/core/src/render3/instructions/property.ts @@ -7,11 +7,11 @@ */ import {bindingUpdated} from '../bindings'; import {SanitizerFn} from '../interfaces/sanitization'; -import {BINDING_INDEX, LView} from '../interfaces/view'; +import {BINDING_INDEX, LView, TVIEW} from '../interfaces/view'; import {getLView, getSelectedIndex} from '../state'; import {NO_CHANGE} from '../tokens'; -import {TsickleIssue1009, elementPropertyInternal, storeBindingMetadata} from './shared'; +import {TsickleIssue1009, elementPropertyInternal, storePropertyBindingMetadata} from './shared'; /** @@ -35,9 +35,11 @@ import {TsickleIssue1009, elementPropertyInternal, storeBindingMetadata} from '. export function ɵɵproperty( propName: string, value: T, sanitizer?: SanitizerFn | null): TsickleIssue1009 { const lView = getLView(); - const bindReconciledValue = bind(lView, value); - if (bindReconciledValue !== NO_CHANGE) { - elementPropertyInternal(getSelectedIndex(), propName, bindReconciledValue, sanitizer); + const bindingIndex = lView[BINDING_INDEX]++; + if (bindingUpdated(lView, bindingIndex, value)) { + const nodeIndex = getSelectedIndex(); + elementPropertyInternal(nodeIndex, propName, value, sanitizer); + ngDevMode && storePropertyBindingMetadata(lView[TVIEW].data, nodeIndex, propName, bindingIndex); } return ɵɵproperty; } @@ -49,7 +51,5 @@ export function ɵɵproperty( * @param value Value to diff */ export function bind(lView: LView, value: T): T|NO_CHANGE { - const bindingIndex = lView[BINDING_INDEX]++; - ngDevMode && storeBindingMetadata(lView); - return bindingUpdated(lView, bindingIndex, value) ? value : NO_CHANGE; + return bindingUpdated(lView, lView[BINDING_INDEX]++, value) ? value : NO_CHANGE; } diff --git a/packages/core/src/render3/instructions/property_interpolation.ts b/packages/core/src/render3/instructions/property_interpolation.ts index 5787487f432a9..60723361a80c4 100644 --- a/packages/core/src/render3/instructions/property_interpolation.ts +++ b/packages/core/src/render3/instructions/property_interpolation.ts @@ -6,11 +6,12 @@ * found in the LICENSE file at https://angular.io/license */ import {SanitizerFn} from '../interfaces/sanitization'; +import {BINDING_INDEX, TVIEW} from '../interfaces/view'; import {getLView, getSelectedIndex} from '../state'; import {NO_CHANGE} from '../tokens'; import {interpolation1, interpolation2, interpolation3, interpolation4, interpolation5, interpolation6, interpolation7, interpolation8, interpolationV} from './interpolation'; -import {TsickleIssue1009, elementPropertyInternal} from './shared'; +import {TsickleIssue1009, elementPropertyInternal, storePropertyBindingMetadata} from './shared'; @@ -81,9 +82,13 @@ export function ɵɵpropertyInterpolate( export function ɵɵpropertyInterpolate1( propName: string, prefix: string, v0: any, suffix: string, sanitizer?: SanitizerFn): TsickleIssue1009 { - const interpolatedValue = interpolation1(getLView(), prefix, v0, suffix); + const lView = getLView(); + const interpolatedValue = interpolation1(lView, prefix, v0, suffix); if (interpolatedValue !== NO_CHANGE) { elementPropertyInternal(getSelectedIndex(), propName, interpolatedValue, sanitizer); + ngDevMode && storePropertyBindingMetadata( + lView[TVIEW].data, getSelectedIndex(), propName, lView[BINDING_INDEX] - 1, + prefix, suffix); } return ɵɵpropertyInterpolate1; } @@ -121,9 +126,14 @@ export function ɵɵpropertyInterpolate1( export function ɵɵpropertyInterpolate2( propName: string, prefix: string, v0: any, i0: string, v1: any, suffix: string, sanitizer?: SanitizerFn): TsickleIssue1009 { - const interpolatedValue = interpolation2(getLView(), prefix, v0, i0, v1, suffix); + const lView = getLView(); + const interpolatedValue = interpolation2(lView, prefix, v0, i0, v1, suffix); if (interpolatedValue !== NO_CHANGE) { - elementPropertyInternal(getSelectedIndex(), propName, interpolatedValue, sanitizer); + const nodeIndex = getSelectedIndex(); + elementPropertyInternal(nodeIndex, propName, interpolatedValue, sanitizer); + ngDevMode && + storePropertyBindingMetadata( + lView[TVIEW].data, nodeIndex, propName, lView[BINDING_INDEX] - 2, prefix, i0, suffix); } return ɵɵpropertyInterpolate2; } @@ -164,9 +174,14 @@ export function ɵɵpropertyInterpolate2( export function ɵɵpropertyInterpolate3( propName: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, suffix: string, sanitizer?: SanitizerFn): TsickleIssue1009 { - const interpolatedValue = interpolation3(getLView(), prefix, v0, i0, v1, i1, v2, suffix); + const lView = getLView(); + const interpolatedValue = interpolation3(lView, prefix, v0, i0, v1, i1, v2, suffix); if (interpolatedValue !== NO_CHANGE) { - elementPropertyInternal(getSelectedIndex(), propName, interpolatedValue, sanitizer); + const nodeIndex = getSelectedIndex(); + elementPropertyInternal(nodeIndex, propName, interpolatedValue, sanitizer); + ngDevMode && storePropertyBindingMetadata( + lView[TVIEW].data, nodeIndex, propName, lView[BINDING_INDEX] - 3, prefix, i0, + i1, suffix); } return ɵɵpropertyInterpolate3; } @@ -209,9 +224,14 @@ export function ɵɵpropertyInterpolate3( export function ɵɵpropertyInterpolate4( propName: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, suffix: string, sanitizer?: SanitizerFn): TsickleIssue1009 { - const interpolatedValue = interpolation4(getLView(), prefix, v0, i0, v1, i1, v2, i2, v3, suffix); + const lView = getLView(); + const interpolatedValue = interpolation4(lView, prefix, v0, i0, v1, i1, v2, i2, v3, suffix); if (interpolatedValue !== NO_CHANGE) { - elementPropertyInternal(getSelectedIndex(), propName, interpolatedValue, sanitizer); + const nodeIndex = getSelectedIndex(); + elementPropertyInternal(nodeIndex, propName, interpolatedValue, sanitizer); + ngDevMode && storePropertyBindingMetadata( + lView[TVIEW].data, nodeIndex, propName, lView[BINDING_INDEX] - 4, prefix, i0, + i1, i2, suffix); } return ɵɵpropertyInterpolate4; } @@ -256,10 +276,15 @@ export function ɵɵpropertyInterpolate4( export function ɵɵpropertyInterpolate5( propName: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, i3: string, v4: any, suffix: string, sanitizer?: SanitizerFn): TsickleIssue1009 { + const lView = getLView(); const interpolatedValue = - interpolation5(getLView(), prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, suffix); + interpolation5(lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, suffix); if (interpolatedValue !== NO_CHANGE) { - elementPropertyInternal(getSelectedIndex(), propName, interpolatedValue, sanitizer); + const nodeIndex = getSelectedIndex(); + elementPropertyInternal(nodeIndex, propName, interpolatedValue, sanitizer); + ngDevMode && storePropertyBindingMetadata( + lView[TVIEW].data, nodeIndex, propName, lView[BINDING_INDEX] - 5, prefix, i0, + i1, i2, i3, suffix); } return ɵɵpropertyInterpolate5; } @@ -307,10 +332,15 @@ export function ɵɵpropertyInterpolate6( propName: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, i3: string, v4: any, i4: string, v5: any, suffix: string, sanitizer?: SanitizerFn): TsickleIssue1009 { + const lView = getLView(); const interpolatedValue = - interpolation6(getLView(), prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, suffix); + interpolation6(lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, suffix); if (interpolatedValue !== NO_CHANGE) { - elementPropertyInternal(getSelectedIndex(), propName, interpolatedValue, sanitizer); + const nodeIndex = getSelectedIndex(); + elementPropertyInternal(nodeIndex, propName, interpolatedValue, sanitizer); + ngDevMode && storePropertyBindingMetadata( + lView[TVIEW].data, nodeIndex, propName, lView[BINDING_INDEX] - 6, prefix, i0, + i1, i2, i3, i4, suffix); } return ɵɵpropertyInterpolate6; } @@ -360,10 +390,15 @@ export function ɵɵpropertyInterpolate7( propName: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, i3: string, v4: any, i4: string, v5: any, i5: string, v6: any, suffix: string, sanitizer?: SanitizerFn): TsickleIssue1009 { - const interpolatedValue = interpolation7( - getLView(), prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, suffix); + const lView = getLView(); + const interpolatedValue = + interpolation7(lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, suffix); if (interpolatedValue !== NO_CHANGE) { - elementPropertyInternal(getSelectedIndex(), propName, interpolatedValue, sanitizer); + const nodeIndex = getSelectedIndex(); + elementPropertyInternal(nodeIndex, propName, interpolatedValue, sanitizer); + ngDevMode && storePropertyBindingMetadata( + lView[TVIEW].data, nodeIndex, propName, lView[BINDING_INDEX] - 7, prefix, i0, + i1, i2, i3, i4, i5, suffix); } return ɵɵpropertyInterpolate7; } @@ -415,10 +450,15 @@ export function ɵɵpropertyInterpolate8( propName: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, i3: string, v4: any, i4: string, v5: any, i5: string, v6: any, i6: string, v7: any, suffix: string, sanitizer?: SanitizerFn): TsickleIssue1009 { + const lView = getLView(); const interpolatedValue = interpolation8( - getLView(), prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, i6, v7, suffix); + lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, i6, v7, suffix); if (interpolatedValue !== NO_CHANGE) { - elementPropertyInternal(getSelectedIndex(), propName, interpolatedValue, sanitizer); + const nodeIndex = getSelectedIndex(); + elementPropertyInternal(nodeIndex, propName, interpolatedValue, sanitizer); + ngDevMode && storePropertyBindingMetadata( + lView[TVIEW].data, nodeIndex, propName, lView[BINDING_INDEX] - 8, prefix, i0, + i1, i2, i3, i4, i5, i6, suffix); } return ɵɵpropertyInterpolate8; } @@ -455,9 +495,20 @@ export function ɵɵpropertyInterpolate8( */ export function ɵɵpropertyInterpolateV( propName: string, values: any[], sanitizer?: SanitizerFn): TsickleIssue1009 { - const interpolatedValue = interpolationV(getLView(), values); + const lView = getLView(); + const interpolatedValue = interpolationV(lView, values); if (interpolatedValue !== NO_CHANGE) { - elementPropertyInternal(getSelectedIndex(), propName, interpolatedValue, sanitizer); + const nodeIndex = getSelectedIndex(); + elementPropertyInternal(nodeIndex, propName, interpolatedValue, sanitizer); + if (ngDevMode) { + const interpolationInBetween = [values[0]]; // prefix + for (let i = 2; i < values.length; i += 2) { + interpolationInBetween.push(values[i]); + } + storePropertyBindingMetadata( + lView[TVIEW].data, nodeIndex, propName, + lView[BINDING_INDEX] - interpolationInBetween.length + 1, ...interpolationInBetween); + } } return ɵɵpropertyInterpolateV; } diff --git a/packages/core/src/render3/instructions/shared.ts b/packages/core/src/render3/instructions/shared.ts index a320529f775dd..c1f3c93071c27 100644 --- a/packages/core/src/render3/instructions/shared.ts +++ b/packages/core/src/render3/instructions/shared.ts @@ -759,8 +759,7 @@ export function createTNode( injectorIndex, // injectorIndex: number -1, // directiveStart: number -1, // directiveEnd: number - -1, // propertyMetadataStartIndex: number - -1, // propertyMetadataEndIndex: number + null, // propertyBindings: number[]|null 0, // flags: TNodeFlags 0, // providerIndexes: TNodeProviderIndexes tagName, // tagName: string|null @@ -784,8 +783,7 @@ export function createTNode( injectorIndex: injectorIndex, directiveStart: -1, directiveEnd: -1, - propertyMetadataStartIndex: -1, - propertyMetadataEndIndex: -1, + propertyBindings: null, flags: 0, providerIndexes: 0, tagName: tagName, @@ -860,11 +858,12 @@ export function elementPropertyInternal( loadRendererFn?: ((tNode: TNode, lView: LView) => Renderer3) | null): void { ngDevMode && assertNotSame(value, NO_CHANGE as any, 'Incoming value should never be NO_CHANGE.'); const lView = getLView(); + const tView = lView[TVIEW]; const element = getNativeByIndex(index, lView) as RElement | RComment; const tNode = getTNode(index, lView); let inputData: PropertyAliases|null|undefined; let dataValue: PropertyAliasValue|undefined; - if (!nativeOnly && (inputData = initializeTNodeInputs(lView[TVIEW], tNode)) && + if (!nativeOnly && (inputData = initializeTNodeInputs(tView, tNode)) && (dataValue = inputData[propName])) { setInputsForProperty(lView, dataValue, value); if (isComponent(tNode)) markDirtyIfOnPush(lView, index + HEADER_OFFSET); @@ -893,8 +892,6 @@ export function elementPropertyInternal( ngDevMode.rendererSetProperty++; } - ngDevMode && savePropertyDebugData(tNode, lView, propName, lView[TVIEW].data, nativeOnly); - const renderer = loadRendererFn ? loadRendererFn(tNode, lView) : lView[RENDERER]; // It is assumed that the sanitizer is only added when the compiler determines that the // property @@ -982,34 +979,6 @@ function matchingSchemas(hostView: LView, tagName: string | null): boolean { return false; } -/** -* Stores debugging data for this property binding on first template pass. -* This enables features like DebugElement.properties. -*/ -function savePropertyDebugData( - tNode: TNode, lView: LView, propName: string, tData: TData, - nativeOnly: boolean | undefined): void { - const lastBindingIndex = lView[BINDING_INDEX] - 1; - - // Bind/interpolation functions save binding metadata in the last binding index, - // but leave the property name blank. If the interpolation delimiter is at the 0 - // index, we know that this is our first pass and the property name still needs to - // be set. - const bindingMetadata = tData[lastBindingIndex] as string; - if (bindingMetadata[0] == INTERPOLATION_DELIMITER) { - tData[lastBindingIndex] = propName + bindingMetadata; - - // We don't want to store indices for host bindings because they are stored in a - // different part of LView (the expando section). - if (!nativeOnly) { - if (tNode.propertyMetadataStartIndex == -1) { - tNode.propertyMetadataStartIndex = lastBindingIndex; - } - tNode.propertyMetadataEndIndex = lastBindingIndex + 1; - } - } -} - /** * Creates an error that should be thrown when encountering an unknown property on an element. * @param propName Name of the invalid property. @@ -1758,27 +1727,45 @@ function executeViewQueryFn( /////////////////////////////// /** - * Creates binding metadata for a particular binding and stores it in - * TView.data. These are generated in order to support DebugElement.properties. + * Stores meta-data for a property binding to be used by TestBed's `DebugElement.properties`. * - * Each binding / interpolation will have one (including attribute bindings) - * because at the time of binding, we don't know to which instruction the binding - * belongs. It is always stored in TView.data at the index of the last binding - * value in LView (e.g. for interpolation8, it would be stored at the index of - * the 8th value). + * In order to support TestBed's `DebugElement.properties` we need to save, for each binding: + * - a bound property name; + * - a static parts of interpolated strings; * - * @param lView The LView that contains the current binding index. - * @param prefix The static prefix string - * @param suffix The static suffix string + * A given property metadata is saved at the binding's index in the `TView.data` (in other words, a + * property binding metadata will be stored in `TView.data` at the same index as a bound value in + * `LView`). Metadata are represented as `INTERPOLATION_DELIMITER`-delimited string with the + * following format: + * - `propertyName` for bound properties; + * - `propertyName�prefix�interpolation_static_part1�..interpolation_static_partN�suffix` for + * interpolated properties. * - * @returns Newly created binding metadata string for this binding or null + * @param tData `TData` where meta-data will be saved; + * @param nodeIndex index of a `TNode` that is a target of the binding; + * @param propertyName bound property name; + * @param bindingIndex binding index in `LView` + * @param interpolationParts static interpolation parts (for property interpolations) */ -export function storeBindingMetadata(lView: LView, prefix = '', suffix = ''): string|null { - const tData = lView[TVIEW].data; - const lastBindingIndex = lView[BINDING_INDEX] - 1; - const value = INTERPOLATION_DELIMITER + prefix + INTERPOLATION_DELIMITER + suffix; - - return tData[lastBindingIndex] == null ? (tData[lastBindingIndex] = value) : null; +export function storePropertyBindingMetadata( + tData: TData, nodeIndex: number, propertyName: string, bindingIndex: number, + ...interpolationParts: string[]) { + // Binding meta-data are stored only the first time a given property instruction is processed. + // Since we don't have a concept of the "first update pass" we need to check for presence of the + // binding meta-data to decide if one should be stored (or if was stored already). + if (tData[bindingIndex] === null) { + const tNode = tData[nodeIndex + HEADER_OFFSET] as TNode; + if (tNode.inputs == null || !tNode.inputs[propertyName]) { + const propBindingIdxs = tNode.propertyBindings || (tNode.propertyBindings = []); + propBindingIdxs.push(bindingIndex); + let bindingMetadata = propertyName; + if (interpolationParts.length > 0) { + bindingMetadata += + INTERPOLATION_DELIMITER + interpolationParts.join(INTERPOLATION_DELIMITER); + } + tData[bindingIndex] = bindingMetadata; + } + } } export const CLEAN_PROMISE = _CLEAN_PROMISE; diff --git a/packages/core/src/render3/interfaces/node.ts b/packages/core/src/render3/interfaces/node.ts index 6296fd7b66f8f..3d311c1420594 100644 --- a/packages/core/src/render3/interfaces/node.ts +++ b/packages/core/src/render3/interfaces/node.ts @@ -260,19 +260,13 @@ export interface TNode { directiveEnd: number; /** - * Stores the first index where property binding metadata is stored for - * this node. + * Stores indexes of property bindings. This field is only set in the ngDevMode and holds indexes + * of property bindings so TestBed can get bound property metadata for a given node. */ - propertyMetadataStartIndex: number; + propertyBindings: number[]|null; /** - * Stores the exclusive final index where property binding metadata is - * stored for this node. - */ - propertyMetadataEndIndex: number; - - /** - * Stores if Node isComponent, isProjected, hasContentQuery, hasClassInput and hasStyleInput + * Stores if Node isComponent, isProjected, hasContentQuery, hasClassInput and hasStyleInput etc. */ flags: TNodeFlags; diff --git a/packages/core/src/render3/util/misc_utils.ts b/packages/core/src/render3/util/misc_utils.ts index 9ff7eb08c31ff..5d0b9ea648cec 100644 --- a/packages/core/src/render3/util/misc_utils.ts +++ b/packages/core/src/render3/util/misc_utils.ts @@ -82,14 +82,6 @@ export function ɵɵresolveBody(element: RElement & {ownerDocument: Document}) { */ export const INTERPOLATION_DELIMITER = `�`; -/** - * Determines whether or not the given string is a property metadata string. - * See storeBindingMetadata(). - */ -export function isPropMetadataString(str: string): boolean { - return str.indexOf(INTERPOLATION_DELIMITER) >= 0; -} - /** * Unwrap a value which might be behind a closure (for forward declaration reasons). */ diff --git a/packages/core/test/bundling/todo/bundle.golden_symbols.json b/packages/core/test/bundling/todo/bundle.golden_symbols.json index 82bc7b031903e..706e77adb84af 100644 --- a/packages/core/test/bundling/todo/bundle.golden_symbols.json +++ b/packages/core/test/bundling/todo/bundle.golden_symbols.json @@ -488,9 +488,6 @@ { "name": "baseResolveDirective" }, - { - "name": "bind" - }, { "name": "bindingUpdated" }, @@ -1457,4 +1454,4 @@ { "name": "ɵɵtextInterpolate1" } -] +] \ No newline at end of file diff --git a/packages/core/test/debug/debug_node_spec.ts b/packages/core/test/debug/debug_node_spec.ts index ea5eba385e45c..0b33aa17480dd 100644 --- a/packages/core/test/debug/debug_node_spec.ts +++ b/packages/core/test/debug/debug_node_spec.ts @@ -33,6 +33,11 @@ class MessageDir { set message(newMessage: string) { this.logger.add(newMessage); } } +@Directive({selector: '[with-title]', inputs: ['title']}) +class WithTitleDir { + title = ''; +} + @Component({ selector: 'child-comp', template: `
@@ -211,6 +216,23 @@ class TestCmptWithPropBindings { title = 'hello'; } +@Component({ + template: ` + + + + + + + + + + +` +}) +class TestCmptWithPropInterpolation { +} + { describe('debug element', () => { let fixture: ComponentFixture; @@ -235,6 +257,8 @@ class TestCmptWithPropBindings { TestCmptWithViewContainerRef, SimpleContentComp, TestCmptWithPropBindings, + TestCmptWithPropInterpolation, + WithTitleDir, ], providers: [Logger], schemas: [NO_ERRORS_SCHEMA], @@ -720,6 +744,35 @@ class TestCmptWithPropBindings { expect(button.properties).toEqual({disabled: true, tabIndex: 1337, title: 'hello'}); }); + it('should include interpolated properties in the properties map', () => { + const fixture = TestBed.createComponent(TestCmptWithPropInterpolation); + fixture.detectChanges(); + + const buttons = fixture.debugElement.children; + + expect(buttons.length).toBe(10); + expect(buttons[0].properties.title).toBe('0'); + expect(buttons[1].properties.title).toBe('a1b'); + expect(buttons[2].properties.title).toBe('a1b2c'); + expect(buttons[3].properties.title).toBe('a1b2c3d'); + expect(buttons[4].properties.title).toBe('a1b2c3d4e'); + expect(buttons[5].properties.title).toBe('a1b2c3d4e5f'); + expect(buttons[6].properties.title).toBe('a1b2c3d4e5f6g'); + expect(buttons[7].properties.title).toBe('a1b2c3d4e5f6g7h'); + expect(buttons[8].properties.title).toBe('a1b2c3d4e5f6g7h8i'); + expect(buttons[9].properties.title).toBe('a1b2c3d4e5f6g7h8i9j'); + }); + + it('should not include directive-shadowed properties in the properties map', () => { + TestBed.overrideTemplate( + TestCmptWithPropInterpolation, ``); + const fixture = TestBed.createComponent(TestCmptWithPropInterpolation); + fixture.detectChanges(); + + const button = fixture.debugElement.query(By.css('button')); + expect(button.properties.title).toBeUndefined(); + }); + it('should trigger events registered via Renderer2', () => { @Component({template: ''}) class TestComponent implements OnInit {