From e16f75db566e7bb22d8b8c1566f8ed5be6874be0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mi=C5=A1ko=20Hevery?= Date: Fri, 11 Oct 2019 12:43:32 -0700 Subject: [PATCH] refactor(ivy): move `bindingIndex` from `LView` to `LFrame` (#33235) `bindingIndex` stores the current location of the bindings in the template function. Because it used to be stored in `LView` that `LView` was not reentrant. This could happen if a binding was a getter and had a side-effect of calling `detectChanges()`. By moving the `bindingIndex` to `LFrame` where all of the global state is kept in reentrant way we correct the issue. PR Close #33235 --- integration/_payload-limits.json | 4 +- packages/core/src/render3/i18n.ts | 10 ++-- .../src/render3/instructions/attribute.ts | 5 +- .../src/render3/instructions/container.ts | 8 ++-- .../core/src/render3/instructions/element.ts | 6 +-- .../render3/instructions/element_container.ts | 6 +-- .../src/render3/instructions/host_property.ts | 8 ++-- .../src/render3/instructions/interpolation.ts | 37 ++++++++------- .../src/render3/instructions/lview_debug.ts | 3 +- .../core/src/render3/instructions/property.ts | 6 +-- .../instructions/property_interpolation.ts | 40 ++++++++-------- .../core/src/render3/instructions/shared.ts | 21 ++++----- .../core/src/render3/instructions/styling.ts | 24 +++------- .../core/src/render3/instructions/text.ts | 7 +-- packages/core/src/render3/interfaces/view.ts | 36 ++++++-------- packages/core/src/render3/pipe.ts | 6 +-- packages/core/src/render3/state.ts | 38 ++++++++++++++- packages/core/src/util/assert.ts | 6 +++ .../test/acceptance/change_detection_spec.ts | 47 +++++++++++++++++++ .../cyclic_import/bundle.golden_symbols.json | 6 +-- .../hello_world/bundle.golden_symbols.json | 6 +-- .../bundling/todo/bundle.golden_symbols.json | 12 ++--- 22 files changed, 204 insertions(+), 138 deletions(-) diff --git a/integration/_payload-limits.json b/integration/_payload-limits.json index 957460e409c72..a4ad46a2435e1 100644 --- a/integration/_payload-limits.json +++ b/integration/_payload-limits.json @@ -12,7 +12,7 @@ "master": { "uncompressed": { "runtime-es2015": 1485, - "main-es2015": 14678, + "main-es2015": 14861, "polyfills-es2015": 36808 } } @@ -64,4 +64,4 @@ } } } -} \ No newline at end of file +} diff --git a/packages/core/src/render3/i18n.ts b/packages/core/src/render3/i18n.ts index 4d4f4eaaadaa8..3df1f9b71d5e8 100644 --- a/packages/core/src/render3/i18n.ts +++ b/packages/core/src/render3/i18n.ts @@ -25,9 +25,9 @@ import {TElementNode, TIcuContainerNode, TNode, TNodeFlags, TNodeType, TProjecti import {RComment, RElement, RText} from './interfaces/renderer'; import {SanitizerFn} from './interfaces/sanitization'; import {isLContainer} from './interfaces/type_checks'; -import {BINDING_INDEX, HEADER_OFFSET, LView, RENDERER, TVIEW, TView, T_HOST} from './interfaces/view'; +import {HEADER_OFFSET, LView, RENDERER, TVIEW, TView, T_HOST} from './interfaces/view'; import {appendChild, applyProjection, createTextNode, nativeRemoveNode} from './node_manipulation'; -import {getIsParent, getLView, getPreviousOrParentTNode, setIsNotParent, setPreviousOrParentTNode} from './state'; +import {getBindingIndex, getIsParent, getLView, getPreviousOrParentTNode, nextBindingIndex, setIsNotParent, setPreviousOrParentTNode} from './state'; import {renderStringify} from './util/misc_utils'; import {getNativeByIndex, getNativeByTNode, getTNode, load} from './util/view_utils'; @@ -669,7 +669,7 @@ export function ɵɵi18nEnd(): void { */ function i18nEndFirstPass(lView: LView, tView: TView) { ngDevMode && assertEqual( - lView[BINDING_INDEX], tView.bindingStartIndex, + getBindingIndex(), tView.bindingStartIndex, 'i18nEnd should be called before any binding'); const rootIndex = i18nIndexStack[i18nIndexStackPointer--]; @@ -1036,7 +1036,7 @@ let shiftsCounter = 0; */ export function ɵɵi18nExp(value: T): TsickleIssue1009 { const lView = getLView(); - if (bindingUpdated(lView, lView[BINDING_INDEX]++, value)) { + if (bindingUpdated(lView, nextBindingIndex(), value)) { changeMask = changeMask | (1 << shiftsCounter); } shiftsCounter++; @@ -1065,7 +1065,7 @@ export function ɵɵi18nApply(index: number) { updateOpCodes = (tI18n as TI18n).update; icus = (tI18n as TI18n).icus; } - const bindingsStartIndex = lView[BINDING_INDEX] - shiftsCounter - 1; + const bindingsStartIndex = getBindingIndex() - shiftsCounter - 1; readUpdateOpCodes(updateOpCodes, icus, bindingsStartIndex, changeMask, lView); // Reset changeMask & maskBit to default for the next update cycle diff --git a/packages/core/src/render3/instructions/attribute.ts b/packages/core/src/render3/instructions/attribute.ts index 3cb12ffd73169..e938ad201d86c 100644 --- a/packages/core/src/render3/instructions/attribute.ts +++ b/packages/core/src/render3/instructions/attribute.ts @@ -7,8 +7,7 @@ */ import {bindingUpdated} from '../bindings'; import {SanitizerFn} from '../interfaces/sanitization'; -import {BINDING_INDEX} from '../interfaces/view'; -import {getLView, getSelectedIndex} from '../state'; +import {getLView, getSelectedIndex, nextBindingIndex} from '../state'; import {TsickleIssue1009, elementAttributeInternal} from './shared'; @@ -31,7 +30,7 @@ export function ɵɵattribute( name: string, value: any, sanitizer?: SanitizerFn | null, namespace?: string): TsickleIssue1009 { const lView = getLView(); - if (bindingUpdated(lView, lView[BINDING_INDEX]++, value)) { + if (bindingUpdated(lView, nextBindingIndex(), value)) { elementAttributeInternal(getSelectedIndex(), name, value, lView, sanitizer, namespace); } return ɵɵattribute; diff --git a/packages/core/src/render3/instructions/container.ts b/packages/core/src/render3/instructions/container.ts index 1a2375786f209..3bc8f87df2eeb 100644 --- a/packages/core/src/render3/instructions/container.ts +++ b/packages/core/src/render3/instructions/container.ts @@ -11,12 +11,12 @@ import {attachPatchData} from '../context_discovery'; import {executeCheckHooks, executeInitAndCheckHooks, incrementInitPhaseFlags, registerPostOrderHooks} from '../hooks'; import {ACTIVE_INDEX, CONTAINER_HEADER_OFFSET, LContainer} from '../interfaces/container'; import {ComponentTemplate} from '../interfaces/definition'; -import {LocalRefExtractor, TAttributes, TContainerNode, TNode, TNodeFlags, TNodeType, TViewNode} from '../interfaces/node'; +import {LocalRefExtractor, TAttributes, TContainerNode, TNode, TNodeType, TViewNode} from '../interfaces/node'; import {isDirectiveHost} from '../interfaces/type_checks'; -import {BINDING_INDEX, FLAGS, HEADER_OFFSET, InitPhaseState, LView, LViewFlags, RENDERER, TVIEW, T_HOST} from '../interfaces/view'; +import {FLAGS, HEADER_OFFSET, InitPhaseState, LView, LViewFlags, RENDERER, TVIEW, T_HOST} from '../interfaces/view'; import {assertNodeType} from '../node_assert'; import {appendChild, removeView} from '../node_manipulation'; -import {getCheckNoChangesMode, getIsParent, getLView, getPreviousOrParentTNode, setIsNotParent, setPreviousOrParentTNode} from '../state'; +import {getBindingIndex, getCheckNoChangesMode, getIsParent, getLView, getPreviousOrParentTNode, setIsNotParent, setPreviousOrParentTNode} from '../state'; import {getNativeByTNode, load} from '../util/view_utils'; import {addToViewTree, createDirectivesInstances, createLContainer, createTNode, createTView, getOrCreateTNode, resolveDirectives, saveResolvedLocalsInData} from './shared'; @@ -172,7 +172,7 @@ function containerInternal( lView: LView, nodeIndex: number, tagName: string | null, attrs: TAttributes | null): TContainerNode { ngDevMode && assertEqual( - lView[BINDING_INDEX], lView[TVIEW].bindingStartIndex, + getBindingIndex(), lView[TVIEW].bindingStartIndex, 'container nodes should be created before any bindings'); const adjustedIndex = nodeIndex + HEADER_OFFSET; diff --git a/packages/core/src/render3/instructions/element.ts b/packages/core/src/render3/instructions/element.ts index 18c99c35ff610..c73cb8b03814b 100644 --- a/packages/core/src/render3/instructions/element.ts +++ b/packages/core/src/render3/instructions/element.ts @@ -14,10 +14,10 @@ import {TAttributes, TNodeFlags, TNodeType} from '../interfaces/node'; import {RElement} from '../interfaces/renderer'; import {StylingMapArray, TStylingContext} from '../interfaces/styling'; import {isContentQueryHost, isDirectiveHost} from '../interfaces/type_checks'; -import {BINDING_INDEX, HEADER_OFFSET, LView, RENDERER, TVIEW, T_HOST} from '../interfaces/view'; +import {HEADER_OFFSET, LView, RENDERER, TVIEW, T_HOST} from '../interfaces/view'; import {assertNodeType} from '../node_assert'; import {appendChild} from '../node_manipulation'; -import {decreaseElementDepthCount, getElementDepthCount, getIsParent, getLView, getNamespace, getPreviousOrParentTNode, getSelectedIndex, increaseElementDepthCount, setIsNotParent, setPreviousOrParentTNode} from '../state'; +import {decreaseElementDepthCount, getBindingIndex, getElementDepthCount, getIsParent, getLView, getNamespace, getPreviousOrParentTNode, getSelectedIndex, increaseElementDepthCount, setIsNotParent, setPreviousOrParentTNode} from '../state'; import {setUpAttributes} from '../util/attrs_utils'; import {getInitialStylingValue, hasClassInput, hasStyleInput, selectClassBasedInputName} from '../util/styling_utils'; import {getNativeByTNode, getTNode} from '../util/view_utils'; @@ -48,7 +48,7 @@ export function ɵɵelementStart( const tViewConsts = tView.consts; const consts = tViewConsts === null || constsIndex == null ? null : tViewConsts[constsIndex]; ngDevMode && assertEqual( - lView[BINDING_INDEX], tView.bindingStartIndex, + getBindingIndex(), tView.bindingStartIndex, 'elements should be created before any bindings'); ngDevMode && ngDevMode.rendererCreateElement++; diff --git a/packages/core/src/render3/instructions/element_container.ts b/packages/core/src/render3/instructions/element_container.ts index 92b8392039958..1882da2c7684a 100644 --- a/packages/core/src/render3/instructions/element_container.ts +++ b/packages/core/src/render3/instructions/element_container.ts @@ -11,10 +11,10 @@ import {attachPatchData} from '../context_discovery'; import {registerPostOrderHooks} from '../hooks'; import {TAttributes, TNodeType} from '../interfaces/node'; import {isContentQueryHost, isDirectiveHost} from '../interfaces/type_checks'; -import {BINDING_INDEX, HEADER_OFFSET, RENDERER, TVIEW, T_HOST} from '../interfaces/view'; +import {HEADER_OFFSET, RENDERER, TVIEW, T_HOST} from '../interfaces/view'; import {assertNodeType} from '../node_assert'; import {appendChild} from '../node_manipulation'; -import {getIsParent, getLView, getPreviousOrParentTNode, setIsNotParent, setPreviousOrParentTNode} from '../state'; +import {getBindingIndex, getIsParent, getLView, getPreviousOrParentTNode, setIsNotParent, setPreviousOrParentTNode} from '../state'; import {createDirectivesInstances, executeContentQueries, getOrCreateTNode, resolveDirectives, saveResolvedLocalsInData} from './shared'; import {registerInitialStylingOnTNode} from './styling'; @@ -44,7 +44,7 @@ export function ɵɵelementContainerStart( const tViewConsts = tView.consts; const consts = tViewConsts === null || constsIndex == null ? null : tViewConsts[constsIndex]; ngDevMode && assertEqual( - lView[BINDING_INDEX], tView.bindingStartIndex, + getBindingIndex(), tView.bindingStartIndex, 'element containers should be created before any bindings'); ngDevMode && ngDevMode.rendererCreateComment++; diff --git a/packages/core/src/render3/instructions/host_property.ts b/packages/core/src/render3/instructions/host_property.ts index d50ac61bf295f..5d378ddfaeb6c 100644 --- a/packages/core/src/render3/instructions/host_property.ts +++ b/packages/core/src/render3/instructions/host_property.ts @@ -7,8 +7,8 @@ */ import {bindingUpdated} from '../bindings'; import {SanitizerFn} from '../interfaces/sanitization'; -import {BINDING_INDEX, TVIEW} from '../interfaces/view'; -import {getLView, getSelectedIndex} from '../state'; +import {TVIEW} from '../interfaces/view'; +import {getLView, getSelectedIndex, nextBindingIndex} from '../state'; import {NO_CHANGE} from '../tokens'; import {TsickleIssue1009, elementPropertyInternal, loadComponentRenderer, storePropertyBindingMetadata} from './shared'; @@ -30,7 +30,7 @@ import {TsickleIssue1009, elementPropertyInternal, loadComponentRenderer, storeP export function ɵɵhostProperty( propName: string, value: T, sanitizer?: SanitizerFn | null): TsickleIssue1009 { const lView = getLView(); - const bindingIndex = lView[BINDING_INDEX]++; + const bindingIndex = nextBindingIndex(); if (bindingUpdated(lView, bindingIndex, value)) { const nodeIndex = getSelectedIndex(); elementPropertyInternal(lView, nodeIndex, propName, value, sanitizer, true); @@ -64,7 +64,7 @@ export function ɵɵhostProperty( export function ɵɵupdateSyntheticHostBinding( propName: string, value: T | NO_CHANGE, sanitizer?: SanitizerFn | null): TsickleIssue1009 { const lView = getLView(); - const bindingIndex = lView[BINDING_INDEX]++; + const bindingIndex = nextBindingIndex(); if (bindingUpdated(lView, bindingIndex, value)) { const nodeIndex = getSelectedIndex(); elementPropertyInternal( diff --git a/packages/core/src/render3/instructions/interpolation.ts b/packages/core/src/render3/instructions/interpolation.ts index 8e57f414d0736..21ef666c231aa 100644 --- a/packages/core/src/render3/instructions/interpolation.ts +++ b/packages/core/src/render3/instructions/interpolation.ts @@ -8,7 +8,8 @@ import {assertEqual, assertLessThan} from '../../util/assert'; import {bindingUpdated, bindingUpdated2, bindingUpdated3, bindingUpdated4} from '../bindings'; -import {BINDING_INDEX, LView} from '../interfaces/view'; +import {LView} from '../interfaces/view'; +import {getBindingIndex, incrementBindingIndex, nextBindingIndex, setBindingIndex} from '../state'; import {NO_CHANGE} from '../tokens'; import {renderStringify} from '../util/misc_utils'; @@ -30,13 +31,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; - let bindingIndex = lView[BINDING_INDEX]; + let bindingIndex = getBindingIndex(); 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; + setBindingIndex(bindingIndex); if (!isBindingUpdated) { return NO_CHANGE; @@ -60,7 +61,7 @@ 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); + const different = bindingUpdated(lView, nextBindingIndex(), v0); return different ? prefix + renderStringify(v0) + suffix : NO_CHANGE; } @@ -69,9 +70,9 @@ export function interpolation1(lView: LView, prefix: string, v0: any, suffix: st */ export function interpolation2( lView: LView, prefix: string, v0: any, i0: string, v1: any, suffix: string): string|NO_CHANGE { - const bindingIndex = lView[BINDING_INDEX]; + const bindingIndex = getBindingIndex(); const different = bindingUpdated2(lView, bindingIndex, v0, v1); - lView[BINDING_INDEX] += 2; + incrementBindingIndex(2); return different ? prefix + renderStringify(v0) + i0 + renderStringify(v1) + suffix : NO_CHANGE; } @@ -82,9 +83,9 @@ export function interpolation2( export function interpolation3( lView: LView, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, suffix: string): string|NO_CHANGE { - const bindingIndex = lView[BINDING_INDEX]; + const bindingIndex = getBindingIndex(); const different = bindingUpdated3(lView, bindingIndex, v0, v1, v2); - lView[BINDING_INDEX] += 3; + incrementBindingIndex(3); return different ? prefix + renderStringify(v0) + i0 + renderStringify(v1) + i1 + renderStringify(v2) + suffix : @@ -97,9 +98,9 @@ export function interpolation3( export function interpolation4( lView: LView, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, suffix: string): string|NO_CHANGE { - const bindingIndex = lView[BINDING_INDEX]; + const bindingIndex = getBindingIndex(); const different = bindingUpdated4(lView, bindingIndex, v0, v1, v2, v3); - lView[BINDING_INDEX] += 4; + incrementBindingIndex(4); return different ? prefix + renderStringify(v0) + i0 + renderStringify(v1) + i1 + renderStringify(v2) + i2 + @@ -113,10 +114,10 @@ export function interpolation4( export function interpolation5( lView: LView, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, i3: string, v4: any, suffix: string): string|NO_CHANGE { - const bindingIndex = lView[BINDING_INDEX]; + const bindingIndex = getBindingIndex(); let different = bindingUpdated4(lView, bindingIndex, v0, v1, v2, v3); different = bindingUpdated(lView, bindingIndex + 4, v4) || different; - lView[BINDING_INDEX] += 5; + incrementBindingIndex(5); return different ? prefix + renderStringify(v0) + i0 + renderStringify(v1) + i1 + renderStringify(v2) + i2 + @@ -130,10 +131,10 @@ export function interpolation5( export function interpolation6( lView: LView, 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): string|NO_CHANGE { - const bindingIndex = lView[BINDING_INDEX]; + const bindingIndex = getBindingIndex(); let different = bindingUpdated4(lView, bindingIndex, v0, v1, v2, v3); different = bindingUpdated2(lView, bindingIndex + 4, v4, v5) || different; - lView[BINDING_INDEX] += 6; + incrementBindingIndex(6); return different ? prefix + renderStringify(v0) + i0 + renderStringify(v1) + i1 + renderStringify(v2) + i2 + @@ -148,10 +149,10 @@ export function interpolation7( lView: LView, 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): string| NO_CHANGE { - const bindingIndex = lView[BINDING_INDEX]; + const bindingIndex = getBindingIndex(); let different = bindingUpdated4(lView, bindingIndex, v0, v1, v2, v3); different = bindingUpdated3(lView, bindingIndex + 4, v4, v5, v6) || different; - lView[BINDING_INDEX] += 7; + incrementBindingIndex(7); return different ? prefix + renderStringify(v0) + i0 + renderStringify(v1) + i1 + renderStringify(v2) + i2 + @@ -167,10 +168,10 @@ export function interpolation8( lView: LView, 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): string|NO_CHANGE { - const bindingIndex = lView[BINDING_INDEX]; + const bindingIndex = getBindingIndex(); let different = bindingUpdated4(lView, bindingIndex, v0, v1, v2, v3); different = bindingUpdated4(lView, bindingIndex + 4, v4, v5, v6, v7) || different; - lView[BINDING_INDEX] += 8; + incrementBindingIndex(8); return different ? prefix + renderStringify(v0) + i0 + renderStringify(v1) + i1 + renderStringify(v2) + i2 + diff --git a/packages/core/src/render3/instructions/lview_debug.ts b/packages/core/src/render3/instructions/lview_debug.ts index 2d63876e1bbcf..41375298a9106 100644 --- a/packages/core/src/render3/instructions/lview_debug.ts +++ b/packages/core/src/render3/instructions/lview_debug.ts @@ -19,7 +19,7 @@ import {SelectorFlags} from '../interfaces/projection'; import {TQueries} from '../interfaces/query'; import {RComment, RElement, RNode} from '../interfaces/renderer'; import {TStylingContext} from '../interfaces/styling'; -import {BINDING_INDEX, CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTEXT, DECLARATION_VIEW, ExpandoInstructions, FLAGS, HEADER_OFFSET, HOST, HookData, INJECTOR, LView, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, RENDERER_FACTORY, SANITIZER, TData, TVIEW, TView as ITView, TView, T_HOST} from '../interfaces/view'; +import {CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTEXT, DECLARATION_VIEW, ExpandoInstructions, FLAGS, HEADER_OFFSET, HOST, HookData, INJECTOR, LView, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, RENDERER_FACTORY, SANITIZER, TData, TVIEW, TView as ITView, TView, T_HOST} from '../interfaces/view'; import {DebugNodeStyling, NodeStylingDebug} from '../styling/styling_debug'; import {attachDebugObject} from '../util/debug_utils'; import {isStylingContext} from '../util/styling_utils'; @@ -325,7 +325,6 @@ export class LViewDebug { get declarationView() { return toDebug(this._raw_lView[DECLARATION_VIEW]); } get queries() { return this._raw_lView[QUERIES]; } get tHost() { return this._raw_lView[T_HOST]; } - get bindingIndex() { return this._raw_lView[BINDING_INDEX]; } /** * Normalized view of child views (and containers) attached at this location. diff --git a/packages/core/src/render3/instructions/property.ts b/packages/core/src/render3/instructions/property.ts index c9edd2f8ea051..81ce8fb988906 100644 --- a/packages/core/src/render3/instructions/property.ts +++ b/packages/core/src/render3/instructions/property.ts @@ -7,8 +7,8 @@ */ import {bindingUpdated} from '../bindings'; import {SanitizerFn} from '../interfaces/sanitization'; -import {BINDING_INDEX, TVIEW} from '../interfaces/view'; -import {getLView, getSelectedIndex} from '../state'; +import {TVIEW} from '../interfaces/view'; +import {getLView, getSelectedIndex, nextBindingIndex} from '../state'; import {TsickleIssue1009, elementPropertyInternal, storePropertyBindingMetadata} from './shared'; @@ -34,7 +34,7 @@ import {TsickleIssue1009, elementPropertyInternal, storePropertyBindingMetadata} export function ɵɵproperty( propName: string, value: T, sanitizer?: SanitizerFn | null): TsickleIssue1009 { const lView = getLView(); - const bindingIndex = lView[BINDING_INDEX]++; + const bindingIndex = nextBindingIndex(); if (bindingUpdated(lView, bindingIndex, value)) { const nodeIndex = getSelectedIndex(); elementPropertyInternal(lView, nodeIndex, propName, value, sanitizer); diff --git a/packages/core/src/render3/instructions/property_interpolation.ts b/packages/core/src/render3/instructions/property_interpolation.ts index def259874f178..672ffff56db50 100644 --- a/packages/core/src/render3/instructions/property_interpolation.ts +++ b/packages/core/src/render3/instructions/property_interpolation.ts @@ -6,8 +6,8 @@ * 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 {TVIEW} from '../interfaces/view'; +import {getBindingIndex, getLView, getSelectedIndex} from '../state'; import {NO_CHANGE} from '../tokens'; import {interpolation1, interpolation2, interpolation3, interpolation4, interpolation5, interpolation6, interpolation7, interpolation8, interpolationV} from './interpolation'; @@ -86,9 +86,9 @@ export function ɵɵpropertyInterpolate1( const interpolatedValue = interpolation1(lView, prefix, v0, suffix); if (interpolatedValue !== NO_CHANGE) { elementPropertyInternal(lView, getSelectedIndex(), propName, interpolatedValue, sanitizer); - ngDevMode && storePropertyBindingMetadata( - lView[TVIEW].data, getSelectedIndex(), propName, lView[BINDING_INDEX] - 1, - prefix, suffix); + ngDevMode && + storePropertyBindingMetadata( + lView[TVIEW].data, getSelectedIndex(), propName, getBindingIndex() - 1, prefix, suffix); } return ɵɵpropertyInterpolate1; } @@ -133,7 +133,7 @@ export function ɵɵpropertyInterpolate2( elementPropertyInternal(lView, nodeIndex, propName, interpolatedValue, sanitizer); ngDevMode && storePropertyBindingMetadata( - lView[TVIEW].data, nodeIndex, propName, lView[BINDING_INDEX] - 2, prefix, i0, suffix); + lView[TVIEW].data, nodeIndex, propName, getBindingIndex() - 2, prefix, i0, suffix); } return ɵɵpropertyInterpolate2; } @@ -179,9 +179,9 @@ export function ɵɵpropertyInterpolate3( if (interpolatedValue !== NO_CHANGE) { const nodeIndex = getSelectedIndex(); elementPropertyInternal(lView, nodeIndex, propName, interpolatedValue, sanitizer); - ngDevMode && storePropertyBindingMetadata( - lView[TVIEW].data, nodeIndex, propName, lView[BINDING_INDEX] - 3, prefix, i0, - i1, suffix); + ngDevMode && + storePropertyBindingMetadata( + lView[TVIEW].data, nodeIndex, propName, getBindingIndex() - 3, prefix, i0, i1, suffix); } return ɵɵpropertyInterpolate3; } @@ -230,8 +230,8 @@ export function ɵɵpropertyInterpolate4( const nodeIndex = getSelectedIndex(); elementPropertyInternal(lView, nodeIndex, propName, interpolatedValue, sanitizer); ngDevMode && storePropertyBindingMetadata( - lView[TVIEW].data, nodeIndex, propName, lView[BINDING_INDEX] - 4, prefix, i0, - i1, i2, suffix); + lView[TVIEW].data, nodeIndex, propName, getBindingIndex() - 4, prefix, i0, i1, + i2, suffix); } return ɵɵpropertyInterpolate4; } @@ -283,8 +283,8 @@ export function ɵɵpropertyInterpolate5( const nodeIndex = getSelectedIndex(); elementPropertyInternal(lView, nodeIndex, propName, interpolatedValue, sanitizer); ngDevMode && storePropertyBindingMetadata( - lView[TVIEW].data, nodeIndex, propName, lView[BINDING_INDEX] - 5, prefix, i0, - i1, i2, i3, suffix); + lView[TVIEW].data, nodeIndex, propName, getBindingIndex() - 5, prefix, i0, i1, + i2, i3, suffix); } return ɵɵpropertyInterpolate5; } @@ -339,8 +339,8 @@ export function ɵɵpropertyInterpolate6( const nodeIndex = getSelectedIndex(); elementPropertyInternal(lView, nodeIndex, propName, interpolatedValue, sanitizer); ngDevMode && storePropertyBindingMetadata( - lView[TVIEW].data, nodeIndex, propName, lView[BINDING_INDEX] - 6, prefix, i0, - i1, i2, i3, i4, suffix); + lView[TVIEW].data, nodeIndex, propName, getBindingIndex() - 6, prefix, i0, i1, + i2, i3, i4, suffix); } return ɵɵpropertyInterpolate6; } @@ -397,8 +397,8 @@ export function ɵɵpropertyInterpolate7( const nodeIndex = getSelectedIndex(); elementPropertyInternal(lView, nodeIndex, propName, interpolatedValue, sanitizer); ngDevMode && storePropertyBindingMetadata( - lView[TVIEW].data, nodeIndex, propName, lView[BINDING_INDEX] - 7, prefix, i0, - i1, i2, i3, i4, i5, suffix); + lView[TVIEW].data, nodeIndex, propName, getBindingIndex() - 7, prefix, i0, i1, + i2, i3, i4, i5, suffix); } return ɵɵpropertyInterpolate7; } @@ -457,8 +457,8 @@ export function ɵɵpropertyInterpolate8( const nodeIndex = getSelectedIndex(); elementPropertyInternal(lView, nodeIndex, propName, interpolatedValue, sanitizer); ngDevMode && storePropertyBindingMetadata( - lView[TVIEW].data, nodeIndex, propName, lView[BINDING_INDEX] - 8, prefix, i0, - i1, i2, i3, i4, i5, i6, suffix); + lView[TVIEW].data, nodeIndex, propName, getBindingIndex() - 8, prefix, i0, i1, + i2, i3, i4, i5, i6, suffix); } return ɵɵpropertyInterpolate8; } @@ -507,7 +507,7 @@ export function ɵɵpropertyInterpolateV( } storePropertyBindingMetadata( lView[TVIEW].data, nodeIndex, propName, - lView[BINDING_INDEX] - interpolationInBetween.length + 1, ...interpolationInBetween); + getBindingIndex() - 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 03ddd89663b9d..5fa7c649b2ee0 100644 --- a/packages/core/src/render3/instructions/shared.ts +++ b/packages/core/src/render3/instructions/shared.ts @@ -10,7 +10,7 @@ import {ErrorHandler} from '../../error_handler'; import {CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA, SchemaMetadata} from '../../metadata/schema'; import {validateAgainstEventAttributes, validateAgainstEventProperties} from '../../sanitization/sanitization'; import {Sanitizer} from '../../sanitization/sanitizer'; -import {assertDataInRange, assertDefined, assertDomNode, assertEqual, assertGreaterThan, assertNotEqual, assertNotSame} from '../../util/assert'; +import {assertDataInRange, assertDefined, assertDomNode, assertEqual, assertGreaterThan, assertLessThanOrEqual, assertNotEqual, assertNotSame} from '../../util/assert'; import {createNamedArrayType} from '../../util/named_array_type'; import {initNgDevMode} from '../../util/ng_dev_mode'; import {normalizeDebugBindingName, normalizeDebugBindingValue} from '../../util/ng_reflect'; @@ -27,10 +27,10 @@ import {AttributeMarker, InitialInputData, InitialInputs, LocalRefExtractor, Pro import {RComment, RElement, RText, Renderer3, RendererFactory3, isProceduralRenderer} from '../interfaces/renderer'; import {SanitizerFn} from '../interfaces/sanitization'; import {isComponentDef, isComponentHost, isContentQueryHost, isLContainer, isRootView} from '../interfaces/type_checks'; -import {BINDING_INDEX, CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTEXT, DECLARATION_VIEW, ExpandoInstructions, FLAGS, HEADER_OFFSET, HOST, INJECTOR, InitPhaseState, LView, LViewFlags, NEXT, PARENT, RENDERER, RENDERER_FACTORY, RootContext, RootContextFlags, SANITIZER, TData, TVIEW, TView, T_HOST} from '../interfaces/view'; +import {CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTEXT, DECLARATION_VIEW, ExpandoInstructions, FLAGS, HEADER_OFFSET, HOST, INJECTOR, InitPhaseState, LView, LViewFlags, NEXT, PARENT, RENDERER, RENDERER_FACTORY, RootContext, RootContextFlags, SANITIZER, TData, TVIEW, TView, T_HOST} from '../interfaces/view'; import {assertNodeOfPossibleTypes} from '../node_assert'; import {isNodeMatchingSelectorList} from '../node_selector_matcher'; -import {ActiveElementFlags, enterView, executeElementExitFn, getBindingsEnabled, getCheckNoChangesMode, getIsParent, getPreviousOrParentTNode, getSelectedIndex, hasActiveElementFlag, incrementActiveDirectiveId, leaveView, leaveViewProcessExit, setActiveHostElement, setBindingRoot, setCheckNoChangesMode, setCurrentDirectiveDef, setCurrentQueryIndex, setPreviousOrParentTNode, setSelectedIndex} from '../state'; +import {ActiveElementFlags, enterView, executeElementExitFn, getBindingIndex, getBindingsEnabled, getCheckNoChangesMode, getIsParent, getPreviousOrParentTNode, getSelectedIndex, hasActiveElementFlag, incrementActiveDirectiveId, leaveView, leaveViewProcessExit, setActiveHostElement, setBindingIndex, setBindingRoot, setCheckNoChangesMode, setCurrentDirectiveDef, setCurrentQueryIndex, setPreviousOrParentTNode, setSelectedIndex} from '../state'; import {renderStylingMap, writeStylingValueDirectly} from '../styling/bindings'; import {NO_CHANGE} from '../tokens'; import {isAnimationProp} from '../util/attrs_utils'; @@ -51,11 +51,11 @@ import {LCleanup, LViewBlueprint, MatchesArray, TCleanup, TNodeConstructor, TNod const _CLEAN_PROMISE = (() => Promise.resolve(null))(); /** Sets the host bindings for the current view. */ -export function setHostBindings(tView: TView, viewData: LView): void { +export function setHostBindings(tView: TView, lView: LView): void { const selectedIndex = getSelectedIndex(); try { if (tView.expandoInstructions !== null) { - let bindingRootIndex = viewData[BINDING_INDEX] = tView.expandoStartIndex; + let bindingRootIndex = setBindingIndex(tView.expandoStartIndex); setBindingRoot(bindingRootIndex); let currentDirectiveIndex = -1; let currentElementIndex = -1; @@ -92,8 +92,8 @@ export function setHostBindings(tView: TView, viewData: LView): void { // are run because this way the first directive ID value is not zero. incrementActiveDirectiveId(); - viewData[BINDING_INDEX] = bindingRootIndex; - const hostCtx = unwrapRNode(viewData[currentDirectiveIndex]); + setBindingIndex(bindingRootIndex); + const hostCtx = unwrapRNode(lView[currentDirectiveIndex]); instruction(RenderFlags.Update, hostCtx, currentElementIndex); } currentDirectiveIndex++; @@ -378,7 +378,7 @@ export function refreshView( try { resetPreOrderHookFlags(lView); - setBindingRoot(lView[BINDING_INDEX] = tView.bindingStartIndex); + setBindingIndex(tView.bindingStartIndex); if (templateFn !== null) { executeTemplate(lView, templateFn, RenderFlags.Update, context); } @@ -666,7 +666,6 @@ function createViewBlueprint(bindingStartIndex: number, initialViewLength: numbe for (let i = 0; i < initialViewLength; i++) { blueprint.push(i < bindingStartIndex ? null : NO_CHANGE); } - blueprint[BINDING_INDEX] = bindingStartIndex; return blueprint as LView; } @@ -1191,8 +1190,8 @@ function postProcessDirective( * A lighter version of postProcessDirective() that is used for the root component. */ function postProcessBaseDirective(lView: LView, hostTNode: TNode, directive: T): void { - ngDevMode && assertEqual( - lView[BINDING_INDEX], lView[TVIEW].bindingStartIndex, + ngDevMode && assertLessThanOrEqual( + getBindingIndex(), lView[TVIEW].bindingStartIndex, 'directives should be created before any bindings'); attachPatchData(directive, lView); const native = getNativeByTNode(hostTNode, lView); diff --git a/packages/core/src/render3/instructions/styling.ts b/packages/core/src/render3/instructions/styling.ts index 8ce466f1f9a34..aaf5e5158a9f0 100644 --- a/packages/core/src/render3/instructions/styling.ts +++ b/packages/core/src/render3/instructions/styling.ts @@ -13,8 +13,8 @@ import {AttributeMarker, TAttributes, TNode, TNodeFlags, TNodeType} from '../int import {RElement} from '../interfaces/renderer'; import {StylingMapArray, StylingMapArrayIndex, TStylingConfig, TStylingContext} from '../interfaces/styling'; import {isDirectiveHost} from '../interfaces/type_checks'; -import {BINDING_INDEX, LView, RENDERER} from '../interfaces/view'; -import {getActiveDirectiveId, getCheckNoChangesMode, getCurrentStyleSanitizer, getLView, getSelectedIndex, resetCurrentStyleSanitizer, setCurrentStyleSanitizer, setElementExitFn} from '../state'; +import {LView, RENDERER} from '../interfaces/view'; +import {getActiveDirectiveId, getCheckNoChangesMode, getCurrentStyleSanitizer, getLView, getSelectedIndex, incrementBindingIndex, nextBindingIndex, resetCurrentStyleSanitizer, setCurrentStyleSanitizer, setElementExitFn} from '../state'; import {applyStylingMapDirectly, applyStylingValueDirectly, flushStyling, setClass, setStyle, updateClassViaContext, updateStyleViaContext} from '../styling/bindings'; import {activateStylingMapFeature} from '../styling/map_based_bindings'; import {attachStylingDebugObject} from '../styling/styling_debug'; @@ -90,13 +90,11 @@ export function ɵɵstyleProp( export function stylePropInternal( elementIndex: number, prop: string, value: string | number | SafeValue | null, suffix?: string | null | undefined): void { - const lView = getLView(); - // if a value is interpolated then it may render a `NO_CHANGE` value. // in this case we do not need to do anything, but the binding index // still needs to be incremented because all styling binding values // are stored inside of the lView. - const bindingIndex = getAndIncrementBindingIndex(lView, false); + const bindingIndex = nextBindingIndex(); const updated = stylingProp(elementIndex, bindingIndex, prop, resolveStylePropValue(value, suffix), false); @@ -124,13 +122,11 @@ export function stylePropInternal( * @codeGenApi */ export function ɵɵclassProp(className: string, value: boolean | null): void { - const lView = getLView(); - // if a value is interpolated then it may render a `NO_CHANGE` value. // in this case we do not need to do anything, but the binding index // still needs to be incremented because all styling binding values // are stored inside of the lView. - const bindingIndex = getAndIncrementBindingIndex(lView, false); + const bindingIndex = nextBindingIndex(); const updated = stylingProp(getSelectedIndex(), bindingIndex, className, value, true); if (ngDevMode) { @@ -248,7 +244,7 @@ export function ɵɵstyleMap(styles: {[styleName: string]: any} | NO_CHANGE | nu // in this case we do not need to do anything, but the binding index // still needs to be incremented because all styling binding values // are stored inside of the lView. - const bindingIndex = getAndIncrementBindingIndex(lView, true); + const bindingIndex = incrementBindingIndex(2); // inputs are only evaluated from a template binding into a directive, therefore, // there should not be a situation where a directive host bindings function @@ -300,7 +296,7 @@ export function classMapInternal( // in this case we do not need to do anything, but the binding index // still needs to be incremented because all styling binding values // are stored inside of the lView. - const bindingIndex = getAndIncrementBindingIndex(lView, true); + const bindingIndex = incrementBindingIndex(2); // inputs are only evaluated from a template binding into a directive, therefore, // there should not be a situation where a directive host bindings function @@ -582,11 +578,3 @@ function resolveStylePropValue( function isHostStyling(): boolean { return isHostStylingActive(getActiveDirectiveId()); } - -function getAndIncrementBindingIndex(lView: LView, isMapBased: boolean): number { - // map-based bindings use two slots because the previously constructed - // className / style value must be compared against. - const index = lView[BINDING_INDEX]; - lView[BINDING_INDEX] += isMapBased ? 2 : 1; - return index; -} diff --git a/packages/core/src/render3/instructions/text.ts b/packages/core/src/render3/instructions/text.ts index 46976d682a15b..b223b7ae1fb2a 100644 --- a/packages/core/src/render3/instructions/text.ts +++ b/packages/core/src/render3/instructions/text.ts @@ -7,9 +7,10 @@ */ import {assertDataInRange, assertEqual} from '../../util/assert'; import {TNodeType} from '../interfaces/node'; -import {BINDING_INDEX, HEADER_OFFSET, RENDERER, TVIEW, T_HOST} from '../interfaces/view'; +import {HEADER_OFFSET, RENDERER, TVIEW, T_HOST} from '../interfaces/view'; import {appendChild, createTextNode} from '../node_manipulation'; -import {getLView, setIsNotParent} from '../state'; +import {getBindingIndex, getLView, setIsNotParent} from '../state'; + import {getOrCreateTNode} from './shared'; @@ -25,7 +26,7 @@ import {getOrCreateTNode} from './shared'; export function ɵɵtext(index: number, value: string = ''): void { const lView = getLView(); ngDevMode && assertEqual( - lView[BINDING_INDEX], lView[TVIEW].bindingStartIndex, + getBindingIndex(), lView[TVIEW].bindingStartIndex, 'text nodes should be created before any bindings'); ngDevMode && assertDataInRange(lView, index + HEADER_OFFSET); const textNative = lView[index + HEADER_OFFSET] = createTextNode(value, lView[RENDERER]); diff --git a/packages/core/src/render3/interfaces/view.ts b/packages/core/src/render3/interfaces/view.ts index f84325f333a96..96cb52487f8f4 100644 --- a/packages/core/src/render3/interfaces/view.ts +++ b/packages/core/src/render3/interfaces/view.ts @@ -32,20 +32,19 @@ export const PARENT = 3; export const NEXT = 4; export const QUERIES = 5; export const T_HOST = 6; -export const BINDING_INDEX = 7; -export const CLEANUP = 8; -export const CONTEXT = 9; -export const INJECTOR = 10; -export const RENDERER_FACTORY = 11; -export const RENDERER = 12; -export const SANITIZER = 13; -export const CHILD_HEAD = 14; -export const CHILD_TAIL = 15; -export const DECLARATION_VIEW = 16; -export const DECLARATION_LCONTAINER = 17; -export const PREORDER_HOOK_FLAGS = 18; +export const CLEANUP = 7; +export const CONTEXT = 8; +export const INJECTOR = 9; +export const RENDERER_FACTORY = 10; +export const RENDERER = 11; +export const SANITIZER = 12; +export const CHILD_HEAD = 13; +export const CHILD_TAIL = 14; +export const DECLARATION_VIEW = 15; +export const DECLARATION_LCONTAINER = 16; +export const PREORDER_HOOK_FLAGS = 17; /** Size of LView's header. Necessary to adjust for it when setting slots. */ -export const HEADER_OFFSET = 19; +export const HEADER_OFFSET = 18; // This interface replaces the real LView interface if it is an arg or a @@ -120,15 +119,6 @@ export interface LView extends Array { */ [T_HOST]: TViewNode|TElementNode|null; - /** - * The binding index we should access next. - * - * This is stored so that bindings can continue where they left off - * if a view is left midway through processing bindings (e.g. if there is - * a setter that creates an embedded view, like in ngIf). - */ - [BINDING_INDEX]: number; - /** * When a view is destroyed, listeners need to be released and outputs need to be * unsubscribed. This context array stores both listener functions wrapped with @@ -381,6 +371,8 @@ export interface TView { * starts to store bindings only. Saving this value ensures that we * will begin reading bindings at the correct point in the array when * we are in update mode. + * + * -1 means that it has not been initialized. */ bindingStartIndex: number; diff --git a/packages/core/src/render3/pipe.ts b/packages/core/src/render3/pipe.ts index ee41c72dc9442..b741e393a9aa8 100644 --- a/packages/core/src/render3/pipe.ts +++ b/packages/core/src/render3/pipe.ts @@ -12,9 +12,9 @@ import {PipeTransform} from '../change_detection/pipe_transform'; import {getFactoryDef} from './definition'; import {store} from './instructions/all'; import {PipeDef, PipeDefList} from './interfaces/definition'; -import {BINDING_INDEX, HEADER_OFFSET, LView, TVIEW} from './interfaces/view'; +import {HEADER_OFFSET, LView, TVIEW} from './interfaces/view'; import {ɵɵpureFunction1, ɵɵpureFunction2, ɵɵpureFunction3, ɵɵpureFunction4, ɵɵpureFunctionV} from './pure_function'; -import {getLView} from './state'; +import {getBindingIndex, getLView} from './state'; import {NO_CHANGE} from './tokens'; import {load} from './util/view_utils'; @@ -200,7 +200,7 @@ function unwrapValue(lView: LView, newValue: any): any { newValue = WrappedValue.unwrap(newValue); // The NO_CHANGE value needs to be written at the index where the impacted binding value is // stored - const bindingToInvalidateIdx = lView[BINDING_INDEX]; + const bindingToInvalidateIdx = getBindingIndex(); lView[bindingToInvalidateIdx] = NO_CHANGE; } return newValue; diff --git a/packages/core/src/render3/state.ts b/packages/core/src/render3/state.ts index 7e81335adcc05..ae3e3b5e46545 100644 --- a/packages/core/src/render3/state.ts +++ b/packages/core/src/render3/state.ts @@ -12,7 +12,7 @@ import {assertDefined, assertEqual} from '../util/assert'; import {assertLViewOrUndefined} from './assert'; import {ComponentDef, DirectiveDef} from './interfaces/definition'; import {TNode} from './interfaces/node'; -import {BINDING_INDEX, CONTEXT, DECLARATION_VIEW, LView, OpaqueViewState, TVIEW} from './interfaces/view'; +import {CONTEXT, DECLARATION_VIEW, LView, OpaqueViewState, TVIEW} from './interfaces/view'; /** @@ -62,6 +62,11 @@ interface LFrame { */ selectedIndex: number; + /** + * Current pointer to the binding index. + */ + bindingIndex: number; + /** * The last viewData retrieved by nextContext(). * Allows building nextContext() and reference() calls. @@ -433,11 +438,38 @@ export function getBindingRoot() { let index = lFrame.bindingRootIndex; if (index === -1) { const lView = lFrame.lView; - index = lFrame.bindingRootIndex = lView[BINDING_INDEX] = lView[TVIEW].bindingStartIndex; + index = lFrame.bindingRootIndex = lView[TVIEW].bindingStartIndex; } return index; } +export function getBindingIndex(): number { + return instructionState.lFrame.bindingIndex; +} + +export function setBindingIndex(value: number): number { + return instructionState.lFrame.bindingIndex = value; +} + +export function nextBindingIndex(): number { + return instructionState.lFrame.bindingIndex++; +} + +export function incrementBindingIndex(count: number): number { + const lFrame = instructionState.lFrame; + const index = lFrame.bindingIndex; + lFrame.bindingIndex = lFrame.bindingIndex + count; + return index; +} + +/** + * Set a new binding root index so that host template functions can execute. + * + * Bindings inside the host template are 0 index. But because we don't know ahead of time + * how many host bindings we have we can't pre-compute them. For this reason they are all + * 0 index and we just shift the root so that they match next available location in the LView. + * @param value + */ export function setBindingRoot(value: number) { instructionState.lFrame.bindingRootIndex = value; } @@ -513,6 +545,7 @@ export function enterView(newView: LView, tNode: TNode | null): void { newLFrame.currentDirectiveDef = null; newLFrame.activeDirectiveId = 0; newLFrame.bindingRootIndex = -1; + newLFrame.bindingIndex = newView === null ? -1 : newView[TVIEW].bindingStartIndex; newLFrame.currentQueryIndex = 0; } @@ -539,6 +572,7 @@ function createLFrame(parent: LFrame | null): LFrame { currentDirectiveDef: null, // activeDirectiveId: 0, // bindingRootIndex: -1, // + bindingIndex: -1, // currentQueryIndex: 0, // parent: parent !, // child: null, // diff --git a/packages/core/src/util/assert.ts b/packages/core/src/util/assert.ts index f5aa73f7cbffe..165241ab1db1b 100644 --- a/packages/core/src/util/assert.ts +++ b/packages/core/src/util/assert.ts @@ -48,6 +48,12 @@ export function assertLessThan(actual: T, expected: T, msg: string) { } } +export function assertLessThanOrEqual(actual: T, expected: T, msg: string) { + if (actual > expected) { + throwError(msg); + } +} + export function assertGreaterThan(actual: T, expected: T, msg: string) { if (actual <= expected) { throwError(msg); diff --git a/packages/core/test/acceptance/change_detection_spec.ts b/packages/core/test/acceptance/change_detection_spec.ts index e947a1befe283..ad51503669517 100644 --- a/packages/core/test/acceptance/change_detection_spec.ts +++ b/packages/core/test/acceptance/change_detection_spec.ts @@ -127,6 +127,53 @@ describe('change detection', () => { fixture.detectChanges(false); expect(fixture.nativeElement).toHaveText('1|dynamic'); }); + + it('should support re-enterant change detection', () => { + @Component({ + selector: 'has-host-binding', + template: '..', + host: { + '[class.x]': 'x', + } + }) + class HasHostBinding { + x = true; + } + + @Component({ + selector: 'child', + template: '', + inputs: ['input'], + }) + class Child { + /** + * @internal + */ + private _input !: number; + + constructor(private cdr: ChangeDetectorRef) {} + + get input() { return this._input; } + + set input(value: number) { + this._input = value; + this.cdr.detectChanges(); + } + } + + @Component({ + selector: 'root', + template: '', + }) + class Root { + } + + TestBed.configureTestingModule({ + declarations: [Root, Child, HasHostBinding], + }); + + TestBed.createComponent(Root).detectChanges(); + }); }); describe('OnPush', () => { 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 1f169c4df8e57..4ea80638e4f3e 100644 --- a/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json +++ b/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json @@ -2,9 +2,6 @@ { "name": "ACTIVE_INDEX" }, - { - "name": "BINDING_INDEX" - }, { "name": "BLOOM_MASK" }, @@ -608,6 +605,9 @@ { "name": "setActiveHostElement" }, + { + "name": "setBindingIndex" + }, { "name": "setBindingRoot" }, 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 2f90cfa8578b7..2b3924e09d71c 100644 --- a/packages/core/test/bundling/hello_world/bundle.golden_symbols.json +++ b/packages/core/test/bundling/hello_world/bundle.golden_symbols.json @@ -2,9 +2,6 @@ { "name": "ACTIVE_INDEX" }, - { - "name": "BINDING_INDEX" - }, { "name": "BLOOM_MASK" }, @@ -437,6 +434,9 @@ { "name": "setActiveHostElement" }, + { + "name": "setBindingIndex" + }, { "name": "setBindingRoot" }, diff --git a/packages/core/test/bundling/todo/bundle.golden_symbols.json b/packages/core/test/bundling/todo/bundle.golden_symbols.json index a82221c3f86e0..cebecb6d3b561 100644 --- a/packages/core/test/bundling/todo/bundle.golden_symbols.json +++ b/packages/core/test/bundling/todo/bundle.golden_symbols.json @@ -2,9 +2,6 @@ { "name": "ACTIVE_INDEX" }, - { - "name": "BINDING_INDEX" - }, { "name": "BIT_MASK_START_VALUE" }, @@ -611,9 +608,6 @@ { "name": "getActiveDirectiveId" }, - { - "name": "getAndIncrementBindingIndex" - }, { "name": "getBeforeNodeForView" }, @@ -1073,6 +1067,9 @@ { "name": "nativeRemoveNode" }, + { + "name": "nextBindingIndex" + }, { "name": "nextContextImpl" }, @@ -1208,6 +1205,9 @@ { "name": "setActiveHostElement" }, + { + "name": "setBindingIndex" + }, { "name": "setBindingRoot" },