X Tutup
Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 17 additions & 12 deletions packages/core/src/render3/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ export function throwErrorIfNoChangesMode(
creationMode: boolean, oldValue: any, currValue: any, propName?: string): never|void {
const field = propName ? ` for '${propName}'` : '';
let msg =
`ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value${field}: '${oldValue}'. Current value: '${currValue}'.`;
`ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. ` +
`Previous value${field}: '${oldValue}'. Current value: '${currValue}'.`;
if (creationMode) {
msg +=
` It seems like the view has been created after its parent and its children have been dirty checked.` +
Expand All @@ -60,7 +61,9 @@ export function throwErrorIfNoChangesMode(

function constructDetailsForInterpolation(
lView: LView, rootIndex: number, expressionIndex: number, meta: string, changedValue: any) {
const [propName, prefix, ...chunks] = meta.split(INTERPOLATION_DELIMITER);
// Split metadata string (e.g. '�propName�Prefix � and � suffix') using INTERPOLATION_DELIMITER
// and extract the necessary chunks
const [unused, propName, prefix, ...chunks] = meta.split(INTERPOLATION_DELIMITER);
let oldValue = prefix, newValue = prefix;
for (let i = 0; i < chunks.length; i++) {
const slotIdx = rootIndex + i;
Expand All @@ -71,12 +74,13 @@ function constructDetailsForInterpolation(
}

/**
* Constructs an object that contains details for the ExpressionChangedAfterItHasBeenCheckedError:
* Constructs an object that contains details for the `ExpressionChangedAfterItHasBeenCheckedError`
* error:
* - property name (for property bindings or interpolations)
* - old and new values, enriched using information from metadata
*
* More information on the metadata storage format can be found in `storePropertyBindingMetadata`
* function description.
* More information on the metadata storage format can be found in `storeBindingMetadata` function
* description.
*/
export function getExpressionChangedErrorDetails(
lView: LView, bindingIndex: number, oldValue: any,
Expand All @@ -85,19 +89,19 @@ export function getExpressionChangedErrorDetails(
const metadata = tData[bindingIndex];

if (typeof metadata === 'string') {
// metadata for property interpolation
// Metadata for property interpolation
if (metadata.indexOf(INTERPOLATION_DELIMITER) > -1) {
return constructDetailsForInterpolation(
lView, bindingIndex, bindingIndex, metadata, newValue);
}
// metadata for property binding
// Metadata for property binding
return {propName: metadata, oldValue, newValue};
}

// metadata is not available for this expression, check if this expression is a part of the
// Metadata is not available for this expression, check if this expression is a part of the
// property interpolation by going from the current binding index left and look for a string that
// contains INTERPOLATION_DELIMITER, the layout in tView.data for this case will look like this:
// [..., 'id�Prefix � and � suffix', null, null, null, ...]
// [..., '�propName�Prefix � and � suffix', null, null, null, ...]
if (metadata === null) {
let idx = bindingIndex - 1;
while (typeof tData[idx] !== 'string' && tData[idx + 1] === null) {
Expand All @@ -106,9 +110,10 @@ export function getExpressionChangedErrorDetails(
const meta = tData[idx];
if (typeof meta === 'string') {
const matches = meta.match(new RegExp(INTERPOLATION_DELIMITER, 'g'));
// first interpolation delimiter separates property name from interpolation parts (in case of
// property interpolations), so we subtract one from total number of found delimiters
if (matches && (matches.length - 1) > bindingIndex - idx) {
// Property or attribute name is wrapped in interpolation delimiters, so we subtract 2 from
// total number of found delimiters in a string. This also works for text interpolations which
// use the same format.
if (matches && (matches.length - 2) > bindingIndex - idx) {
return constructDetailsForInterpolation(lView, idx, bindingIndex, meta, newValue);
}
}
Expand Down
8 changes: 4 additions & 4 deletions packages/core/src/render3/instructions/host_property.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {TVIEW} from '../interfaces/view';
import {getLView, getSelectedIndex, nextBindingIndex} from '../state';
import {NO_CHANGE} from '../tokens';

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

/**
* Update a property on a host element. Only applies to native node properties, not inputs.
Expand All @@ -23,7 +23,7 @@ import {elementPropertyInternal, loadComponentRenderer, storePropertyBindingMeta
* @param value New value to write.
* @param sanitizer An optional function used to sanitize the value.
* @returns This function returns itself so that it may be chained
* (e.g. `property('name', ctx.name)('title', ctx.title)`)
* (e.g. `hostProperty('name', ctx.name)('title', ctx.title)`)
*
* @codeGenApi
*/
Expand All @@ -34,7 +34,7 @@ export function ɵɵhostProperty<T>(
if (bindingUpdated(lView, bindingIndex, value)) {
const nodeIndex = getSelectedIndex();
elementPropertyInternal(lView, nodeIndex, propName, value, sanitizer, true);
ngDevMode && storePropertyBindingMetadata(lView[TVIEW].data, nodeIndex, propName, bindingIndex);
ngDevMode && storeBindingMetadata(lView[TVIEW].data, nodeIndex, propName, bindingIndex);
}
return ɵɵhostProperty;
}
Expand Down Expand Up @@ -70,7 +70,7 @@ export function ɵɵupdateSyntheticHostBinding<T>(
const nodeIndex = getSelectedIndex();
elementPropertyInternal(
lView, nodeIndex, propName, value, sanitizer, true, loadComponentRenderer);
ngDevMode && storePropertyBindingMetadata(lView[TVIEW].data, nodeIndex, propName, bindingIndex);
ngDevMode && storeBindingMetadata(lView[TVIEW].data, nodeIndex, propName, bindingIndex);
}
return ɵɵupdateSyntheticHostBinding;
}
4 changes: 2 additions & 2 deletions packages/core/src/render3/instructions/property.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {SanitizerFn} from '../interfaces/sanitization';
import {TVIEW} from '../interfaces/view';
import {getLView, getSelectedIndex, nextBindingIndex} from '../state';

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


/**
Expand Down Expand Up @@ -38,7 +38,7 @@ export function ɵɵproperty<T>(
if (bindingUpdated(lView, bindingIndex, value)) {
const nodeIndex = getSelectedIndex();
elementPropertyInternal(lView, nodeIndex, propName, value, sanitizer);
ngDevMode && storePropertyBindingMetadata(lView[TVIEW].data, nodeIndex, propName, bindingIndex);
ngDevMode && storeBindingMetadata(lView[TVIEW].data, nodeIndex, propName, bindingIndex);
}
return ɵɵproperty;
}
20 changes: 10 additions & 10 deletions packages/core/src/render3/instructions/property_interpolation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {getBindingIndex, getLView, getSelectedIndex} from '../state';
import {NO_CHANGE} from '../tokens';

import {interpolation1, interpolation2, interpolation3, interpolation4, interpolation5, interpolation6, interpolation7, interpolation8, interpolationV} from './interpolation';
import {elementPropertyInternal, storePropertyBindingMetadata} from './shared';
import {elementPropertyInternal, storeBindingMetadata} from './shared';



Expand Down Expand Up @@ -87,7 +87,7 @@ export function ɵɵpropertyInterpolate1(
if (interpolatedValue !== NO_CHANGE) {
elementPropertyInternal(lView, getSelectedIndex(), propName, interpolatedValue, sanitizer);
ngDevMode &&
storePropertyBindingMetadata(
storeBindingMetadata(
lView[TVIEW].data, getSelectedIndex(), propName, getBindingIndex() - 1, prefix, suffix);
}
return ɵɵpropertyInterpolate1;
Expand Down Expand Up @@ -132,7 +132,7 @@ export function ɵɵpropertyInterpolate2(
const nodeIndex = getSelectedIndex();
elementPropertyInternal(lView, nodeIndex, propName, interpolatedValue, sanitizer);
ngDevMode &&
storePropertyBindingMetadata(
storeBindingMetadata(
lView[TVIEW].data, nodeIndex, propName, getBindingIndex() - 2, prefix, i0, suffix);
}
return ɵɵpropertyInterpolate2;
Expand Down Expand Up @@ -180,7 +180,7 @@ export function ɵɵpropertyInterpolate3(
const nodeIndex = getSelectedIndex();
elementPropertyInternal(lView, nodeIndex, propName, interpolatedValue, sanitizer);
ngDevMode &&
storePropertyBindingMetadata(
storeBindingMetadata(
lView[TVIEW].data, nodeIndex, propName, getBindingIndex() - 3, prefix, i0, i1, suffix);
}
return ɵɵpropertyInterpolate3;
Expand Down Expand Up @@ -229,7 +229,7 @@ export function ɵɵpropertyInterpolate4(
if (interpolatedValue !== NO_CHANGE) {
const nodeIndex = getSelectedIndex();
elementPropertyInternal(lView, nodeIndex, propName, interpolatedValue, sanitizer);
ngDevMode && storePropertyBindingMetadata(
ngDevMode && storeBindingMetadata(
lView[TVIEW].data, nodeIndex, propName, getBindingIndex() - 4, prefix, i0, i1,
i2, suffix);
}
Expand Down Expand Up @@ -283,7 +283,7 @@ export function ɵɵpropertyInterpolate5(
if (interpolatedValue !== NO_CHANGE) {
const nodeIndex = getSelectedIndex();
elementPropertyInternal(lView, nodeIndex, propName, interpolatedValue, sanitizer);
ngDevMode && storePropertyBindingMetadata(
ngDevMode && storeBindingMetadata(
lView[TVIEW].data, nodeIndex, propName, getBindingIndex() - 5, prefix, i0, i1,
i2, i3, suffix);
}
Expand Down Expand Up @@ -339,7 +339,7 @@ export function ɵɵpropertyInterpolate6(
if (interpolatedValue !== NO_CHANGE) {
const nodeIndex = getSelectedIndex();
elementPropertyInternal(lView, nodeIndex, propName, interpolatedValue, sanitizer);
ngDevMode && storePropertyBindingMetadata(
ngDevMode && storeBindingMetadata(
lView[TVIEW].data, nodeIndex, propName, getBindingIndex() - 6, prefix, i0, i1,
i2, i3, i4, suffix);
}
Expand Down Expand Up @@ -397,7 +397,7 @@ export function ɵɵpropertyInterpolate7(
if (interpolatedValue !== NO_CHANGE) {
const nodeIndex = getSelectedIndex();
elementPropertyInternal(lView, nodeIndex, propName, interpolatedValue, sanitizer);
ngDevMode && storePropertyBindingMetadata(
ngDevMode && storeBindingMetadata(
lView[TVIEW].data, nodeIndex, propName, getBindingIndex() - 7, prefix, i0, i1,
i2, i3, i4, i5, suffix);
}
Expand Down Expand Up @@ -457,7 +457,7 @@ export function ɵɵpropertyInterpolate8(
if (interpolatedValue !== NO_CHANGE) {
const nodeIndex = getSelectedIndex();
elementPropertyInternal(lView, nodeIndex, propName, interpolatedValue, sanitizer);
ngDevMode && storePropertyBindingMetadata(
ngDevMode && storeBindingMetadata(
lView[TVIEW].data, nodeIndex, propName, getBindingIndex() - 8, prefix, i0, i1,
i2, i3, i4, i5, i6, suffix);
}
Expand Down Expand Up @@ -506,7 +506,7 @@ export function ɵɵpropertyInterpolateV(
for (let i = 2; i < values.length; i += 2) {
interpolationInBetween.push(values[i]);
}
storePropertyBindingMetadata(
storeBindingMetadata(
lView[TVIEW].data, nodeIndex, propName,
getBindingIndex() - interpolationInBetween.length + 1, ...interpolationInBetween);
}
Expand Down
41 changes: 19 additions & 22 deletions packages/core/src/render3/instructions/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1796,42 +1796,39 @@ function executeViewQueryFn<T>(
///////////////////////////////

/**
* Stores meta-data for a property binding to be used by TestBed's `DebugElement.properties`.
* Stores metadata for a property binding or interpolation, attribute binding or interpolation,
* text interpolations. This information is used to provide more context for the
* `ExpressionChangedAfterItHasBeenCheckedError` error.
*
* 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;
* 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
* is represented as `INTERPOLATION_DELIMITER`-delimited string with the following format:
* - `propertyName` for bound properties and attributes
* - `�propertyName�prefix�interpolation_static_part1�..interpolation_static_partN�suffix` for
* interpolated properties, interpolated attributes and text interpolations. Note: the
* `propertyName` part is empty in case of text interpolations
*
* 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.
*
* @param tData `TData` where meta-data will be saved;
* @param nodeIndex index of a `TNode` that is a target of the binding;
* @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 storePropertyBindingMetadata(
tData: TData, nodeIndex: number, propertyName: string, bindingIndex: number,
export function storeBindingMetadata(
tData: TData, nodeIndex: number, propertyName: string | null, 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]) {
if (!propertyName || (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);
}
const bindingMetadata = interpolationParts.length === 0 ?
propertyName :
INTERPOLATION_DELIMITER + (propertyName || '') + INTERPOLATION_DELIMITER +
interpolationParts.join(INTERPOLATION_DELIMITER);
tData[bindingIndex] = bindingMetadata;
}
}
Expand Down
Loading
X Tutup