From d44a212b0203b9eaabc44d4b6c86a79b27af8e78 Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Sun, 21 Mar 2021 13:06:09 +0100 Subject: [PATCH] refactor(core): explore approach to avoid storing LView in __ngContext__ **Note:** this is mostly an exploratory PR to check whether the approach actually works and to start a discussion. There's more work to be done to make it mergeable (e.g. it introduces new circular dependencies, there are no unit tests etc.). Explores adding a unique ID to each `LView` which is stored in `__ngContext__`, rather than the LView itself. This will avoid worsening memory leaks where a detached element is retained along with its `LView`. Also reworks `LContext` to store the ID of its `LView`, rather than the `LView` itself, because the `LContext` can also be stored in `__ngContext__`. --- goldens/circular-deps/packages.json | 31 +++++ packages/core/src/debug/debug_node.ts | 12 +- .../core/src/linker/view_container_ref.ts | 4 +- .../core/src/render3/context_discovery.ts | 33 +++-- .../core/src/render3/instructions/shared.ts | 31 ++++- .../core/src/render3/interfaces/context.ts | 5 +- packages/core/src/render3/interfaces/view.ts | 8 +- .../core/src/render3/node_manipulation.ts | 26 +--- .../core/src/render3/util/discovery_utils.ts | 29 ++-- packages/core/src/render3/view_ref.ts | 4 +- packages/core/test/acceptance/debug_spec.ts | 5 +- packages/core/test/acceptance/i18n_spec.ts | 82 +++++------ .../test/acceptance/ngdevmode_debug_spec.ts | 6 +- .../cyclic_import/bundle.golden_symbols.json | 3 + .../forms_reactive/bundle.golden_symbols.json | 6 + .../bundle.golden_symbols.json | 6 + .../hello_world/bundle.golden_symbols.json | 3 + .../router/bundle.golden_symbols.json | 6 + .../bundling/todo/bundle.golden_symbols.json | 6 + .../i18n/i18n_insert_before_index_spec.ts | 128 +++++++++--------- .../core/test/render3/i18n/i18n_parse_spec.ts | 26 ++-- packages/core/test/render3/i18n/i18n_spec.ts | 16 +-- .../core/test/render3/integration_spec.ts | 29 ++-- .../render3/perf/view_destroy_hook/index.ts | 3 +- packages/core/test/render3/render_util.ts | 3 +- 25 files changed, 299 insertions(+), 212 deletions(-) diff --git a/goldens/circular-deps/packages.json b/goldens/circular-deps/packages.json index cae92bc9715827..c268fc9ff2b866 100644 --- a/goldens/circular-deps/packages.json +++ b/goldens/circular-deps/packages.json @@ -108,6 +108,33 @@ "packages/compiler/src/render3/view/styling_builder.ts", "packages/compiler/src/render3/view/template.ts" ], + [ + "packages/core/src/change_detection/change_detection.ts", + "packages/core/src/change_detection/change_detector_ref.ts", + "packages/core/src/render3/view_ref.ts", + "packages/core/src/render3/collect_native_nodes.ts", + "packages/core/src/render3/node_manipulation.ts", + "packages/core/src/render3/context_discovery.ts", + "packages/core/src/render3/instructions/shared.ts", + "packages/core/src/error_handler.ts", + "packages/core/src/errors.ts", + "packages/core/src/view/types.ts", + "packages/core/src/linker/component_factory.ts" + ], + [ + "packages/core/src/change_detection/change_detection.ts", + "packages/core/src/change_detection/change_detector_ref.ts", + "packages/core/src/render3/view_ref.ts", + "packages/core/src/render3/collect_native_nodes.ts", + "packages/core/src/render3/node_manipulation.ts", + "packages/core/src/render3/util/view_traversal_utils.ts", + "packages/core/src/render3/context_discovery.ts", + "packages/core/src/render3/instructions/shared.ts", + "packages/core/src/error_handler.ts", + "packages/core/src/errors.ts", + "packages/core/src/view/types.ts", + "packages/core/src/linker/component_factory.ts" + ], [ "packages/core/src/change_detection/change_detection.ts", "packages/core/src/change_detection/change_detector_ref.ts", @@ -209,6 +236,10 @@ "packages/core/src/metadata/ng_module.ts", "packages/core/src/render3/jit/module.ts" ], + [ + "packages/core/src/render3/context_discovery.ts", + "packages/core/src/render3/instructions/shared.ts" + ], [ "packages/core/src/render3/interfaces/container.ts", "packages/core/src/render3/interfaces/node.ts", diff --git a/packages/core/src/debug/debug_node.ts b/packages/core/src/debug/debug_node.ts index 1fb53711a0bd6a..3af97af53570fb 100644 --- a/packages/core/src/debug/debug_node.ts +++ b/packages/core/src/debug/debug_node.ts @@ -8,6 +8,7 @@ import {Injector} from '../di/injector'; import {assertTNodeForLView} from '../render3/assert'; +import {getLViewById} from '../render3/instructions/shared'; import {CONTAINER_HEADER_OFFSET, LContainer, NATIVE} from '../render3/interfaces/container'; import {TElementNode, TNode, TNodeFlags, TNodeType} from '../render3/interfaces/node'; import {isComponentHost, isLContainer} from '../render3/interfaces/type_checks'; @@ -263,7 +264,7 @@ class DebugElement__POST_R3__ extends DebugNode__POST_R3__ implements DebugEleme get name(): string { try { const context = loadLContext(this.nativeNode)!; - const lView = context.lView; + const lView = getLViewById(context.lViewId)!; // TODO: use assertion function const tData = lView[TVIEW].data; const tNode = tData[context.nodeIndex] as TNode; return tNode.value!; @@ -290,7 +291,7 @@ class DebugElement__POST_R3__ extends DebugNode__POST_R3__ implements DebugEleme return {}; } - const lView = context.lView; + const lView = getLViewById(context.lViewId)!; // TODO: use assertion function const tData = lView[TVIEW].data; const tNode = tData[context.nodeIndex] as TNode; @@ -316,7 +317,7 @@ class DebugElement__POST_R3__ extends DebugNode__POST_R3__ implements DebugEleme return {}; } - const lView = context.lView; + const lView = getLViewById(context.lViewId)!; // TODO: use assertion function const tNodeAttrs = (lView[TVIEW].data[context.nodeIndex] as TNode).attrs; const lowercaseTNodeAttrs: string[] = []; @@ -503,9 +504,10 @@ function _queryAllR3( matches: DebugElement[]|DebugNode[], elementsOnly: boolean) { const context = loadLContext(parentElement.nativeNode, false); if (context !== null) { - const parentTNode = context.lView[TVIEW].data[context.nodeIndex] as TNode; + const lView = getLViewById(context.lViewId)!; // TODO: use assertion function + const parentTNode = lView[TVIEW].data[context.nodeIndex] as TNode; _queryNodeChildrenR3( - parentTNode, context.lView, predicate, matches, elementsOnly, parentElement.nativeNode); + parentTNode, lView, predicate, matches, elementsOnly, parentElement.nativeNode); } else { // If the context is null, then `parentElement` was either created with Renderer2 or native DOM // APIs. diff --git a/packages/core/src/linker/view_container_ref.ts b/packages/core/src/linker/view_container_ref.ts index b37e423efc2486..f2b7cfb532904e 100644 --- a/packages/core/src/linker/view_container_ref.ts +++ b/packages/core/src/linker/view_container_ref.ts @@ -9,7 +9,7 @@ import {Injector} from '../di/injector'; import {assertNodeInjector} from '../render3/assert'; import {getParentInjectorLocation, NodeInjector} from '../render3/di'; -import {addToViewTree, createLContainer} from '../render3/instructions/shared'; +import {addToViewTree, createLContainer, destroyLView} from '../render3/instructions/shared'; import {CONTAINER_HEADER_OFFSET, LContainer, NATIVE, VIEW_REFS} from '../render3/interfaces/container'; import {NodeInjectorOffset} from '../render3/interfaces/injector'; import {TContainerNode, TDirectiveHostNode, TElementContainerNode, TElementNode, TNodeType} from '../render3/interfaces/node'; @@ -17,7 +17,7 @@ import {RComment, RElement} from '../render3/interfaces/renderer_dom'; import {isLContainer} from '../render3/interfaces/type_checks'; import {LView, PARENT, RENDERER, T_HOST, TVIEW} from '../render3/interfaces/view'; import {assertTNodeType} from '../render3/node_assert'; -import {addViewToContainer, destroyLView, detachView, getBeforeNodeForView, insertView, nativeInsertBefore, nativeNextSibling, nativeParentNode} from '../render3/node_manipulation'; +import {addViewToContainer, detachView, getBeforeNodeForView, insertView, nativeInsertBefore, nativeNextSibling, nativeParentNode} from '../render3/node_manipulation'; import {getCurrentTNode, getLView} from '../render3/state'; import {getParentInjectorIndex, getParentInjectorView, hasParentInjector} from '../render3/util/injector_utils'; import {getNativeByTNode, unwrapRNode, viewAttachedToContainer} from '../render3/util/view_utils'; diff --git a/packages/core/src/render3/context_discovery.ts b/packages/core/src/render3/context_discovery.ts index 43f4207eb7ad22..03a1eacffab352 100644 --- a/packages/core/src/render3/context_discovery.ts +++ b/packages/core/src/render3/context_discovery.ts @@ -8,12 +8,12 @@ import '../util/ng_dev_mode'; import {assertDefined, assertDomNode} from '../util/assert'; - import {EMPTY_ARRAY} from '../util/empty'; +import {getLViewById} from './instructions/shared'; import {LContext} from './interfaces/context'; import {TNode, TNodeFlags} from './interfaces/node'; import {RElement, RNode} from './interfaces/renderer_dom'; -import {CONTEXT, HEADER_OFFSET, HOST, LView, TVIEW} from './interfaces/view'; +import {CONTEXT, HEADER_OFFSET, HOST, ID, LView, TVIEW} from './interfaces/view'; import {getComponentLViewByIndex, unwrapRNode} from './util/view_utils'; @@ -109,7 +109,7 @@ export function getLContext(target: any): LContext|null { if (Array.isArray(parentContext)) { lView = parentContext as LView; } else { - lView = parentContext.lView; + lView = getLViewById(parentContext.lViewId)!; // TODO: use assertion function } // the edge of the app was also reached here through another means @@ -137,7 +137,7 @@ export function getLContext(target: any): LContext|null { */ function createLContext(lView: LView, nodeIndex: number, native: RNode): LContext { return { - lView, + lViewId: lView[ID], nodeIndex, native, component: undefined, @@ -153,19 +153,20 @@ function createLContext(lView: LView, nodeIndex: number, native: RNode): LContex * @returns The component's view */ export function getComponentViewByInstance(componentInstance: {}): LView { - let lView = readPatchedData(componentInstance); + let patchedData = readPatchedData(componentInstance); let view: LView; - if (Array.isArray(lView)) { - const nodeIndex = findViaComponent(lView, componentInstance); - view = getComponentLViewByIndex(nodeIndex, lView); - const context = createLContext(lView, nodeIndex, view[HOST] as RElement); + if (Array.isArray(patchedData)) { + const nodeIndex = findViaComponent(patchedData, componentInstance); + view = getComponentLViewByIndex(nodeIndex, patchedData); + const context = createLContext(patchedData, nodeIndex, view[HOST] as RElement); context.component = componentInstance; attachPatchData(componentInstance, context); attachPatchData(context.native, context); } else { - const context = lView as any as LContext; - view = getComponentLViewByIndex(context.nodeIndex, context.lView); + const context = patchedData as any as LContext; + const lView = getLViewById(context.lViewId)!; // TODO: use assertion function + view = getComponentLViewByIndex(context.nodeIndex, lView); } return view; } @@ -181,7 +182,7 @@ const MONKEY_PATCH_KEY_NAME = '__ngContext__'; */ export function attachPatchData(target: any, data: LView|LContext) { ngDevMode && assertDefined(target, 'Target expected'); - target[MONKEY_PATCH_KEY_NAME] = data; + target[MONKEY_PATCH_KEY_NAME] = Array.isArray(data) ? data[ID] : data; } /** @@ -190,13 +191,17 @@ export function attachPatchData(target: any, data: LView|LContext) { */ export function readPatchedData(target: any): LView|LContext|null { ngDevMode && assertDefined(target, 'Target expected'); - return target[MONKEY_PATCH_KEY_NAME] || null; + const data = target[MONKEY_PATCH_KEY_NAME]; + if (typeof data === 'number') { + return getLViewById(data); + } + return data || null; } export function readPatchedLView(target: any): LView|null { const value = readPatchedData(target); if (value) { - return Array.isArray(value) ? value : (value as LContext).lView; + return Array.isArray(value) ? value : getLViewById(value.lViewId); } return null; } diff --git a/packages/core/src/render3/instructions/shared.ts b/packages/core/src/render3/instructions/shared.ts index 6bf02d590ac389..a5925877b30290 100644 --- a/packages/core/src/render3/instructions/shared.ts +++ b/packages/core/src/render3/instructions/shared.ts @@ -33,9 +33,9 @@ import {isProceduralRenderer, Renderer3, RendererFactory3} from '../interfaces/r import {RComment, RElement, RNode, RText} from '../interfaces/renderer_dom'; import {SanitizerFn} from '../interfaces/sanitization'; import {isComponentDef, isComponentHost, isContentQueryHost, isRootView} from '../interfaces/type_checks'; -import {CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTEXT, DECLARATION_COMPONENT_VIEW, DECLARATION_VIEW, FLAGS, HEADER_OFFSET, HOST, HostBindingOpCodes, InitPhaseState, INJECTOR, LView, LViewFlags, NEXT, PARENT, RENDERER, RENDERER_FACTORY, RootContext, RootContextFlags, SANITIZER, T_HOST, TData, TRANSPLANTED_VIEWS_TO_REFRESH, TVIEW, TView, TViewType} from '../interfaces/view'; +import {CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTEXT, DECLARATION_COMPONENT_VIEW, DECLARATION_VIEW, FLAGS, HEADER_OFFSET, HOST, HostBindingOpCodes, ID, InitPhaseState, INJECTOR, LView, LViewFlags, NEXT, PARENT, RENDERER, RENDERER_FACTORY, RootContext, RootContextFlags, SANITIZER, T_HOST, TData, TRANSPLANTED_VIEWS_TO_REFRESH, TVIEW, TView, TViewType} from '../interfaces/view'; import {assertPureTNodeType, assertTNodeType} from '../node_assert'; -import {updateTextNode} from '../node_manipulation'; +import {applyView, destroyViewTree, updateTextNode, WalkTNodeTreeAction} from '../node_manipulation'; import {isInlineTemplate, isNodeMatchingSelectorList} from '../node_selector_matcher'; import {enterView, getBindingsEnabled, getCurrentDirectiveIndex, getCurrentParentTNode, getCurrentTNode, getCurrentTNodePlaceholderOk, getSelectedIndex, isCurrentTNodeParent, isInCheckNoChangesMode, isInI18nBlock, leaveView, setBindingIndex, setBindingRootForHostBindings, setCurrentDirectiveIndex, setCurrentQueryIndex, setCurrentTNode, setIsInCheckNoChangesMode, setSelectedIndex} from '../state'; import {NO_CHANGE} from '../tokens'; @@ -123,6 +123,8 @@ function renderChildComponents(hostLView: LView, components: number[]): void { } } +const ACTIVE_LVIEWS: (LView|null)[] = []; + export function createLView( parentLView: LView|null, tView: TView, context: T|null, flags: LViewFlags, host: RElement|null, tHostNode: TNode|null, rendererFactory: RendererFactory3|null, renderer: Renderer3|null, @@ -142,6 +144,7 @@ export function createLView( lView[SANITIZER] = sanitizer || parentLView && parentLView[SANITIZER] || null!; lView[INJECTOR as any] = injector || parentLView && parentLView[INJECTOR] || null; lView[T_HOST] = tHostNode; + lView[ID] = ACTIVE_LVIEWS.push(lView) - 1; ngDevMode && assertEqual( tView.type == TViewType.Embedded ? parentLView !== null : true, true, @@ -152,6 +155,30 @@ export function createLView( return lView; } +export function getLViewById(id: number): LView|null { + return ACTIVE_LVIEWS[id] || null; +} + + +/** + * A standalone function which destroys an LView, + * conducting clean up (e.g. removing listeners, calling onDestroys). + * + * @param tView The `TView' of the `LView` to be destroyed + * @param lView The view to be destroyed. + */ +export function destroyLView(tView: TView, lView: LView) { + if (!(lView[FLAGS] & LViewFlags.Destroyed)) { + const renderer = lView[RENDERER]; + if (isProceduralRenderer(renderer) && renderer.destroyNode) { + ACTIVE_LVIEWS[lView[ID]] = null; + applyView(tView, lView, renderer, WalkTNodeTreeAction.Destroy, null, null); + } + + destroyViewTree(lView); + } +} + /** * Create and stores the TNode, and hooks it up to the tree. * diff --git a/packages/core/src/render3/interfaces/context.ts b/packages/core/src/render3/interfaces/context.ts index 6f78e268918d53..8bb31bfecbf63d 100644 --- a/packages/core/src/render3/interfaces/context.ts +++ b/packages/core/src/render3/interfaces/context.ts @@ -8,7 +8,6 @@ import {RNode} from './renderer_dom'; -import {LView} from './view'; /** @@ -23,9 +22,9 @@ import {LView} from './view'; */ export interface LContext { /** - * The component's parent view data. + * ID of the component's parent view data. */ - lView: LView; + lViewId: number; /** * The index instance of the node. diff --git a/packages/core/src/render3/interfaces/view.ts b/packages/core/src/render3/interfaces/view.ts index 64e3b020529c49..7ff92d8f1b151f 100644 --- a/packages/core/src/render3/interfaces/view.ts +++ b/packages/core/src/render3/interfaces/view.ts @@ -47,6 +47,7 @@ export const DECLARATION_COMPONENT_VIEW = 16; export const DECLARATION_LCONTAINER = 17; export const PREORDER_HOOK_FLAGS = 18; export const QUERIES = 19; +export const ID = 20; /** * Size of LView's header. Necessary to adjust for it when setting slots. * @@ -54,7 +55,7 @@ export const QUERIES = 19; * instruction index into `LView` index. All other indexes should be in the `LView` index space and * there should be no need to refer to `HEADER_OFFSET` anywhere else. */ -export const HEADER_OFFSET = 20; +export const HEADER_OFFSET = 21; // This interface replaces the real LView interface if it is an arg or a @@ -326,6 +327,9 @@ export interface LView extends Array { * are not `Dirty`/`CheckAlways`. */ [TRANSPLANTED_VIEWS_TO_REFRESH]: number; + + /** Unique ID of the view. */ + [ID]: number; } /** Flags associated with an LView (saved in LView[FLAGS]) */ @@ -1157,4 +1161,4 @@ export interface NodeInjectorDebug { * Location of the parent `TNode`. */ parentInjectorIndex: number; -} \ No newline at end of file +} diff --git a/packages/core/src/render3/node_manipulation.ts b/packages/core/src/render3/node_manipulation.ts index 801957fe21dc5d..57ff92de647311 100644 --- a/packages/core/src/render3/node_manipulation.ts +++ b/packages/core/src/render3/node_manipulation.ts @@ -33,7 +33,7 @@ import {getNativeByTNode, unwrapRNode, updateTransplantedViewCount} from './util const unusedValueToPlacateAjd = unused1 + unused2 + unused3 + unused4 + unused5; -const enum WalkTNodeTreeAction { +export const enum WalkTNodeTreeAction { /** node create in the native environment. Run on initial creation. */ Create = 0, @@ -380,24 +380,6 @@ export function detachView(lContainer: LContainer, removeIndex: number): LView|u return viewToDetach; } -/** - * A standalone function which destroys an LView, - * conducting clean up (e.g. removing listeners, calling onDestroys). - * - * @param tView The `TView' of the `LView` to be destroyed - * @param lView The view to be destroyed. - */ -export function destroyLView(tView: TView, lView: LView) { - if (!(lView[FLAGS] & LViewFlags.Destroyed)) { - const renderer = lView[RENDERER]; - if (isProceduralRenderer(renderer) && renderer.destroyNode) { - applyView(tView, lView, renderer, WalkTNodeTreeAction.Destroy, null, null); - } - - destroyViewTree(lView); - } -} - /** * Calls onDestroys hooks for all directives and pipes in a given view and then removes all * listeners. Listeners are removed as the last step so events delivered in the onDestroys hooks @@ -901,13 +883,13 @@ function applyNodes( * @param parentRElement parent DOM element for insertion (Removal does not need it). * @param beforeNode Before which node the insertions should happen. */ -function applyView( +export function applyView( tView: TView, lView: LView, renderer: Renderer3, action: WalkTNodeTreeAction.Destroy, parentRElement: null, beforeNode: null): void; -function applyView( +export function applyView( tView: TView, lView: LView, renderer: Renderer3, action: WalkTNodeTreeAction, parentRElement: RElement|null, beforeNode: RNode|null): void; -function applyView( +export function applyView( tView: TView, lView: LView, renderer: Renderer3, action: WalkTNodeTreeAction, parentRElement: RElement|null, beforeNode: RNode|null): void { applyNodes(renderer, action, tView.firstChild, lView, parentRElement, beforeNode, false); diff --git a/packages/core/src/render3/util/discovery_utils.ts b/packages/core/src/render3/util/discovery_utils.ts index 1966517cd487bc..0c7208a7ff4f74 100644 --- a/packages/core/src/render3/util/discovery_utils.ts +++ b/packages/core/src/render3/util/discovery_utils.ts @@ -12,6 +12,7 @@ import {assertLView} from '../assert'; import {discoverLocalRefs, getComponentAtNodeIndex, getDirectivesAtNodeIndex, getLContext} from '../context_discovery'; import {NodeInjector} from '../di'; import {buildDebugNode} from '../instructions/lview_debug'; +import {getLViewById} from '../instructions/shared'; import {LContext} from '../interfaces/context'; import {DirectiveDef} from '../interfaces/definition'; import {TElementNode, TNode, TNodeProviderIndexes} from '../interfaces/node'; @@ -54,7 +55,8 @@ export function getComponent(element: Element): T|null { if (context === null) return null; if (context.component === undefined) { - context.component = getComponentAtNodeIndex(context.nodeIndex, context.lView); + const lView = getLViewById(context.lViewId)!; // TODO: use assertion function + context.component = getComponentAtNodeIndex(context.nodeIndex, lView); } return context.component as T; @@ -76,7 +78,9 @@ export function getComponent(element: Element): T|null { export function getContext(element: Element): T|null { assertDomElement(element); const context = loadLContext(element, false); - return context === null ? null : context.lView[CONTEXT] as T; + const lView = + context === null ? null : getLViewById(context.lViewId)!; // TODO: use assertion function + return lView === null ? null : lView[CONTEXT] as T; } /** @@ -98,7 +102,7 @@ export function getOwningComponent(elementOrDir: Element|{}): T|null { const context = loadLContext(elementOrDir, false); if (context === null) return null; - let lView = context.lView; + let lView = getLViewById(context.lViewId)!; // TODO: use assertion function let parent: LView|null; ngDevMode && assertLView(lView); while (lView[TVIEW].type === TViewType.Embedded && (parent = getLViewParent(lView)!)) { @@ -136,8 +140,9 @@ export function getInjector(elementOrDir: Element|{}): Injector { const context = loadLContext(elementOrDir, false); if (context === null) return Injector.NULL; - const tNode = context.lView[TVIEW].data[context.nodeIndex] as TElementNode; - return new NodeInjector(tNode, context.lView); + const lView = getLViewById(context.lViewId)!; // TODO: use assertion function + const tNode = lView[TVIEW].data[context.nodeIndex] as TElementNode; + return new NodeInjector(tNode, lView); } /** @@ -148,7 +153,7 @@ export function getInjector(elementOrDir: Element|{}): Injector { export function getInjectionTokens(element: Element): any[] { const context = loadLContext(element, false); if (context === null) return []; - const lView = context.lView; + const lView = getLViewById(context.lViewId)!; // TODO: use assertion function const tView = lView[TVIEW]; const tNode = tView.data[context.nodeIndex] as TNode; const providerTokens: any[] = []; @@ -195,7 +200,8 @@ export function getDirectives(element: Element): {}[] { const context = loadLContext(element)!; if (context.directives === undefined) { - context.directives = getDirectivesAtNodeIndex(context.nodeIndex, context.lView, false); + const lView = getLViewById(context.lViewId)!; // TODO: use assertion function + context.directives = getDirectivesAtNodeIndex(context.nodeIndex, lView, false); } // The `directives` in this case are a named array called `LComponentView`. Clone the @@ -232,7 +238,8 @@ export function getLocalRefs(target: {}): {[key: string]: any} { if (context === null) return {}; if (context.localRefs === undefined) { - context.localRefs = discoverLocalRefs(context.lView, context.nodeIndex); + const lView = getLViewById(context.lViewId)!; // TODO: use assertion function + context.localRefs = discoverLocalRefs(lView, context.nodeIndex); } return context.localRefs || {}; @@ -327,7 +334,7 @@ export function getListeners(element: Element): Listener[] { const lContext = loadLContext(element, false); if (lContext === null) return []; - const lView = lContext.lView; + const lView = getLViewById(lContext.lViewId)!; // TODO: use assertion function const tView = lView[TVIEW]; const lCleanup = lView[CLEANUP]; const tCleanup = tView.cleanup; @@ -380,7 +387,7 @@ export function getDebugNode(element: Element): DebugNode|null { let debugNode: DebugNode|null = null; const lContext = loadLContextFromNode(element); - const lView = lContext.lView; + const lView = getLViewById(lContext.lViewId)!; // TODO: use assertion function const nodeIndex = lContext.nodeIndex; if (nodeIndex !== -1) { const valueInLView = lView[nodeIndex]; @@ -407,7 +414,7 @@ export function getDebugNode(element: Element): DebugNode|null { export function getComponentLView(target: any): LView { const lContext = loadLContext(target); const nodeIndx = lContext.nodeIndex; - const lView = lContext.lView; + const lView = getLViewById(lContext.lViewId)!; // TODO: use assertion function const componentLView = lView[nodeIndx]; ngDevMode && assertLView(componentLView); return componentLView; diff --git a/packages/core/src/render3/view_ref.ts b/packages/core/src/render3/view_ref.ts index 8f7fdc72fa08b8..9b9a3d20032b16 100644 --- a/packages/core/src/render3/view_ref.ts +++ b/packages/core/src/render3/view_ref.ts @@ -11,11 +11,11 @@ import {EmbeddedViewRef as viewEngine_EmbeddedViewRef, InternalViewRef as viewEn import {removeFromArray} from '../util/array_utils'; import {assertEqual} from '../util/assert'; import {collectNativeNodes} from './collect_native_nodes'; -import {checkNoChangesInRootView, checkNoChangesInternal, detectChangesInRootView, detectChangesInternal, markViewDirty, storeCleanupWithContext} from './instructions/shared'; +import {checkNoChangesInRootView, checkNoChangesInternal, destroyLView, detectChangesInRootView, detectChangesInternal, markViewDirty, storeCleanupWithContext} from './instructions/shared'; import {CONTAINER_HEADER_OFFSET, VIEW_REFS} from './interfaces/container'; import {isLContainer} from './interfaces/type_checks'; import {CONTEXT, FLAGS, LView, LViewFlags, PARENT, TVIEW} from './interfaces/view'; -import {destroyLView, detachView, renderDetachView} from './node_manipulation'; +import {detachView, renderDetachView} from './node_manipulation'; diff --git a/packages/core/test/acceptance/debug_spec.ts b/packages/core/test/acceptance/debug_spec.ts index 436d134d8bac85..2c121d2c89a8b5 100644 --- a/packages/core/test/acceptance/debug_spec.ts +++ b/packages/core/test/acceptance/debug_spec.ts @@ -9,6 +9,7 @@ import {Component} from '@angular/core'; import {getLContext} from '@angular/core/src/render3/context_discovery'; import {LViewDebug} from '@angular/core/src/render3/instructions/lview_debug'; +import {getLViewById} from '@angular/core/src/render3/instructions/shared'; import {TNodeType} from '@angular/core/src/render3/interfaces/node'; import {HEADER_OFFSET} from '@angular/core/src/render3/interfaces/view'; import {TestBed} from '@angular/core/testing'; @@ -27,7 +28,7 @@ onlyInIvy('Ivy specific').describe('Debug Representation', () => { const fixture = TestBed.createComponent(MyComponent); fixture.detectChanges(); - const hostView = getLContext(fixture.componentInstance)!.lView.debug!; + const hostView = getLViewById(getLContext(fixture.componentInstance)!.lViewId)!.debug!; expect(hostView.hostHTML).toEqual(null); const myCompView = hostView.childViews[0] as LViewDebug; expect(myCompView.hostHTML).toContain('
Hello World
'); @@ -47,7 +48,7 @@ onlyInIvy('Ivy specific').describe('Debug Representation', () => { const fixture = TestBed.createComponent(MyComponent); fixture.detectChanges(); - const hostView = getLContext(fixture.componentInstance)!.lView.debug!; + const hostView = getLViewById(getLContext(fixture.componentInstance)!.lViewId)!.debug!; const myComponentView = hostView.childViews[0] as LViewDebug; expect(myComponentView.decls).toEqual({ start: HEADER_OFFSET, diff --git a/packages/core/test/acceptance/i18n_spec.ts b/packages/core/test/acceptance/i18n_spec.ts index cc7fb156ce2c01..d430787feb0073 100644 --- a/packages/core/test/acceptance/i18n_spec.ts +++ b/packages/core/test/acceptance/i18n_spec.ts @@ -652,12 +652,12 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => { const lViewDebug = lView.debug!; fixture.detectChanges(); expect((fixture.nativeElement as Element).textContent).toEqual('just now'); - expect(lViewDebug.nodes.map(toTypeContent)).toEqual(['IcuContainer()']); + expect(lViewDebug.nodes.map(toTypeContent)).toEqual(['IcuContainer()']); // We want to ensure that the ICU container does not have any content! // This is because the content is instance dependent and therefore can't be shared // across `TNode`s. expect(lViewDebug.nodes[0].children.map(toTypeContent)).toEqual([]); - expect(fixture.nativeElement.innerHTML).toEqual('just now'); + expect(fixture.nativeElement.innerHTML).toEqual('just now'); }); it('should support multiple ICUs', () => { @@ -674,15 +674,15 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => { `); const lView = getComponentLView(fixture.componentInstance); expect(lView.debug!.nodes.map(toTypeContent)).toEqual([ - 'IcuContainer()', 'IcuContainer()', + 'IcuContainer()', ]); // We want to ensure that the ICU container does not have any content! // This is because the content is instance dependent and therefore can't be shared // across `TNode`s. expect(lView.debug!.nodes[0].children.map(toTypeContent)).toEqual([]); expect(fixture.nativeElement.innerHTML) - .toEqual('just nowMr. Angular'); + .toEqual('just nowMr. Angular'); }); }); }); @@ -778,19 +778,19 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => { other {({{name}})} }`); expect(fixture.nativeElement.innerHTML) - .toEqual(`
aucun email! - (Angular)
`); + .toEqual(`
aucun email! - (Angular)
`); fixture.componentRef.instance.count = 4; fixture.detectChanges(); expect(fixture.nativeElement.innerHTML) .toEqual( - `
4 emails - (Angular)
`); + `
4 emails - (Angular)
`); fixture.componentRef.instance.count = 0; fixture.componentRef.instance.name = 'John'; fixture.detectChanges(); expect(fixture.nativeElement.innerHTML) - .toEqual(`
aucun email! - (John)
`); + .toEqual(`
aucun email! - (John)
`); }); it('with custom interpolation config', () => { @@ -829,9 +829,9 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => { expect(fixture.nativeElement.innerHTML) .toEqual( `
` + - `aucun email!` + + `aucun email!` + ` - ` + - `(Angular)` + + `(Angular)` + `
`); fixture.componentRef.instance.count = 4; @@ -839,9 +839,9 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => { expect(fixture.nativeElement.innerHTML) .toEqual( `
` + - `4 emails` + + `4 emails` + ` - ` + - `(Angular)` + + `(Angular)` + `
`); fixture.componentRef.instance.count = 0; @@ -850,9 +850,9 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => { expect(fixture.nativeElement.innerHTML) .toEqual( `
` + - `aucun email!` + + `aucun email!` + ` - ` + - `(John)` + + `(John)` + `
`); }); @@ -867,7 +867,7 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => { other {({{name}})} }`); expect(fixture.nativeElement.innerHTML) - .toEqual(`
(Angular)
`); @@ -887,7 +887,7 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => { other {({{name}})} }`); expect(fixture.nativeElement.innerHTML) - .toEqual(`(Angular)`); + .toEqual(`(Angular)`); }); it('inside ', () => { @@ -922,12 +922,12 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => { other {animals} }!} }`); - expect(fixture.nativeElement.innerHTML).toEqual(`
zero
`); + expect(fixture.nativeElement.innerHTML).toEqual(`
zero
`); fixture.componentRef.instance.count = 4; fixture.detectChanges(); expect(fixture.nativeElement.innerHTML) - .toEqual(`
4 animaux!
`); + .toEqual(`
4 animaux!
`); }); it('nested with interpolations in "other" blocks', () => { @@ -947,16 +947,16 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => { }!} other {other - {{count}}} }`); - expect(fixture.nativeElement.innerHTML).toEqual(`
zero
`); + expect(fixture.nativeElement.innerHTML).toEqual(`
zero
`); fixture.componentRef.instance.count = 2; fixture.detectChanges(); expect(fixture.nativeElement.innerHTML) - .toEqual(`
2 animaux!
`); + .toEqual(`
2 animaux!
`); fixture.componentRef.instance.count = 4; fixture.detectChanges(); - expect(fixture.nativeElement.innerHTML).toEqual(`
autre - 4
`); + expect(fixture.nativeElement.innerHTML).toEqual(`
autre - 4
`); }); it('should return the correct plural form for ICU expressions when using "ro" locale', () => { @@ -989,31 +989,31 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => { =other {lots of emails} }`); - expect(fixture.nativeElement.innerHTML).toEqual('no email'); + expect(fixture.nativeElement.innerHTML).toEqual('no email'); // Change detection cycle, no model changes fixture.detectChanges(); - expect(fixture.nativeElement.innerHTML).toEqual('no email'); + expect(fixture.nativeElement.innerHTML).toEqual('no email'); fixture.componentInstance.count = 3; fixture.detectChanges(); - expect(fixture.nativeElement.innerHTML).toEqual('a few emails'); + expect(fixture.nativeElement.innerHTML).toEqual('a few emails'); fixture.componentInstance.count = 1; fixture.detectChanges(); - expect(fixture.nativeElement.innerHTML).toEqual('one email'); + expect(fixture.nativeElement.innerHTML).toEqual('one email'); fixture.componentInstance.count = 10; fixture.detectChanges(); - expect(fixture.nativeElement.innerHTML).toEqual('a few emails'); + expect(fixture.nativeElement.innerHTML).toEqual('a few emails'); fixture.componentInstance.count = 20; fixture.detectChanges(); - expect(fixture.nativeElement.innerHTML).toEqual('lots of emails'); + expect(fixture.nativeElement.innerHTML).toEqual('lots of emails'); fixture.componentInstance.count = 0; fixture.detectChanges(); - expect(fixture.nativeElement.innerHTML).toEqual('no email'); + expect(fixture.nativeElement.innerHTML).toEqual('no email'); }); it(`should return the correct plural form for ICU expressions when using "es" locale`, () => { @@ -1040,31 +1040,31 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => { =other {lots of emails} }`); - expect(fixture.nativeElement.innerHTML).toEqual('no email'); + expect(fixture.nativeElement.innerHTML).toEqual('no email'); // Change detection cycle, no model changes fixture.detectChanges(); - expect(fixture.nativeElement.innerHTML).toEqual('no email'); + expect(fixture.nativeElement.innerHTML).toEqual('no email'); fixture.componentInstance.count = 3; fixture.detectChanges(); - expect(fixture.nativeElement.innerHTML).toEqual('lots of emails'); + expect(fixture.nativeElement.innerHTML).toEqual('lots of emails'); fixture.componentInstance.count = 1; fixture.detectChanges(); - expect(fixture.nativeElement.innerHTML).toEqual('one email'); + expect(fixture.nativeElement.innerHTML).toEqual('one email'); fixture.componentInstance.count = 10; fixture.detectChanges(); - expect(fixture.nativeElement.innerHTML).toEqual('lots of emails'); + expect(fixture.nativeElement.innerHTML).toEqual('lots of emails'); fixture.componentInstance.count = 20; fixture.detectChanges(); - expect(fixture.nativeElement.innerHTML).toEqual('lots of emails'); + expect(fixture.nativeElement.innerHTML).toEqual('lots of emails'); fixture.componentInstance.count = 0; fixture.detectChanges(); - expect(fixture.nativeElement.innerHTML).toEqual('no email'); + expect(fixture.nativeElement.innerHTML).toEqual('no email'); }); it('projection', () => { @@ -1159,12 +1159,12 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => { const fixture = TestBed.createComponent(App); fixture.detectChanges(); expect(fixture.debugElement.nativeElement.innerHTML) - .toContain('
ONE
'); + .toContain('
ONE
'); fixture.componentRef.instance.count = 2; fixture.detectChanges(); expect(fixture.debugElement.nativeElement.innerHTML) - .toContain('
OTHER
'); + .toContain('
OTHER
'); // destroy component fixture.componentInstance.condition = false; @@ -1176,7 +1176,7 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => { fixture.componentInstance.count = 1; fixture.detectChanges(); expect(fixture.debugElement.nativeElement.innerHTML) - .toContain('
ONE
'); + .toContain('
ONE
'); }); it('with nested ICU expression and inside a container when creating a view via vcr.createEmbeddedView', @@ -1248,12 +1248,12 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => { fixture.detectChanges(); expect(fixture.debugElement.nativeElement.innerHTML) .toBe( - '
2 animals!
'); + '
2 animals!
'); fixture.componentRef.instance.count = 1; fixture.detectChanges(); expect(fixture.debugElement.nativeElement.innerHTML) - .toBe('
ONE
'); + .toBe('
ONE
'); }); it('with nested containers', () => { @@ -2357,13 +2357,13 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => { fixture.detectChanges(); expect(fixture.nativeElement.innerHTML) .toEqual( - `
Contenu enfant et projection depuis Parent
`); + `
Contenu enfant et projection depuis Parent
`); fixture.componentRef.instance.name = 'angular'; fixture.detectChanges(); expect(fixture.nativeElement.innerHTML) .toEqual( - `
Contenu enfant et projection depuis Angular
`); + `
Contenu enfant et projection depuis Angular
`); }); it(`shouldn't project deleted projections in i18n blocks`, () => { diff --git a/packages/core/test/acceptance/ngdevmode_debug_spec.ts b/packages/core/test/acceptance/ngdevmode_debug_spec.ts index ef07bb26caf73c..996969c8a8c39d 100644 --- a/packages/core/test/acceptance/ngdevmode_debug_spec.ts +++ b/packages/core/test/acceptance/ngdevmode_debug_spec.ts @@ -8,7 +8,7 @@ import {CommonModule} from '@angular/common'; import {Component} from '@angular/core'; -import {LView} from '@angular/core/src/render3/interfaces/view'; +import {getLViewById} from '@angular/core/src/render3/instructions/shared'; import {getComponentLView, loadLContext} from '@angular/core/src/render3/util/discovery_utils'; import {createNamedArrayType} from '@angular/core/src/util/named_array_type'; import {TestBed} from '@angular/core/testing'; @@ -32,7 +32,7 @@ onlyInIvy('Debug information exist in ivy only').describe('ngDevMode debug', () TestBed.configureTestingModule({declarations: [MyApp], imports: [CommonModule]}); const fixture = TestBed.createComponent(MyApp); - const rootLView = loadLContext(fixture.nativeElement).lView; + const rootLView = getLViewById(loadLContext(fixture.nativeElement).lViewId)!; expect(rootLView.constructor.name).toEqual('LRootView'); const componentLView = getComponentLView(fixture.componentInstance); @@ -41,7 +41,7 @@ onlyInIvy('Debug information exist in ivy only').describe('ngDevMode debug', () const element: HTMLElement = fixture.nativeElement; fixture.detectChanges(); const li = element.querySelector('li')!; - const embeddedLView = loadLContext(li).lView; + const embeddedLView = getLViewById(loadLContext(li).lViewId)!; expect(embeddedLView.constructor.name).toEqual('LEmbeddedView_MyApp_li_1'); }); }); 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 dd29afd07a7305..b84a7fa26b50e9 100644 --- a/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json +++ b/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json @@ -1,4 +1,7 @@ [ + { + "name": "ACTIVE_LVIEWS" + }, { "name": "CLEAN_PROMISE" }, diff --git a/packages/core/test/bundling/forms_reactive/bundle.golden_symbols.json b/packages/core/test/bundling/forms_reactive/bundle.golden_symbols.json index 809a19e62f25a4..4b829add99f4e7 100644 --- a/packages/core/test/bundling/forms_reactive/bundle.golden_symbols.json +++ b/packages/core/test/bundling/forms_reactive/bundle.golden_symbols.json @@ -1,4 +1,7 @@ [ + { + "name": "ACTIVE_LVIEWS" + }, { "name": "ALLOW_MULTIPLE_PLATFORMS" }, @@ -968,6 +971,9 @@ { "name": "getLView" }, + { + "name": "getLViewById" + }, { "name": "getLViewParent" }, diff --git a/packages/core/test/bundling/forms_template_driven/bundle.golden_symbols.json b/packages/core/test/bundling/forms_template_driven/bundle.golden_symbols.json index 0baae6f79ec9db..cfe31c74e4fe61 100644 --- a/packages/core/test/bundling/forms_template_driven/bundle.golden_symbols.json +++ b/packages/core/test/bundling/forms_template_driven/bundle.golden_symbols.json @@ -1,4 +1,7 @@ [ + { + "name": "ACTIVE_LVIEWS" + }, { "name": "ALLOW_MULTIPLE_PLATFORMS" }, @@ -932,6 +935,9 @@ { "name": "getLView" }, + { + "name": "getLViewById" + }, { "name": "getLViewParent" }, 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 9f68ceb496c712..46a3262b51edd7 100644 --- a/packages/core/test/bundling/hello_world/bundle.golden_symbols.json +++ b/packages/core/test/bundling/hello_world/bundle.golden_symbols.json @@ -1,4 +1,7 @@ [ + { + "name": "ACTIVE_LVIEWS" + }, { "name": "CLEAN_PROMISE" }, diff --git a/packages/core/test/bundling/router/bundle.golden_symbols.json b/packages/core/test/bundling/router/bundle.golden_symbols.json index 643193002d1f82..fd4e14c5515fbd 100644 --- a/packages/core/test/bundling/router/bundle.golden_symbols.json +++ b/packages/core/test/bundling/router/bundle.golden_symbols.json @@ -1,4 +1,7 @@ [ + { + "name": "ACTIVE_LVIEWS" + }, { "name": "ALLOW_MULTIPLE_PLATFORMS" }, @@ -1331,6 +1334,9 @@ { "name": "getLView" }, + { + "name": "getLViewById" + }, { "name": "getLViewParent" }, diff --git a/packages/core/test/bundling/todo/bundle.golden_symbols.json b/packages/core/test/bundling/todo/bundle.golden_symbols.json index 057cd33ab4948f..849f7dd58f0246 100644 --- a/packages/core/test/bundling/todo/bundle.golden_symbols.json +++ b/packages/core/test/bundling/todo/bundle.golden_symbols.json @@ -1,4 +1,7 @@ [ + { + "name": "ACTIVE_LVIEWS" + }, { "name": "CLEAN_PROMISE" }, @@ -380,6 +383,9 @@ { "name": "getLView" }, + { + "name": "getLViewById" + }, { "name": "getLViewParent" }, diff --git a/packages/core/test/render3/i18n/i18n_insert_before_index_spec.ts b/packages/core/test/render3/i18n/i18n_insert_before_index_spec.ts index 2921d9a2e71e0f..e0b4b4f5828f37 100644 --- a/packages/core/test/render3/i18n/i18n_insert_before_index_spec.ts +++ b/packages/core/test/render3/i18n/i18n_insert_before_index_spec.ts @@ -29,9 +29,9 @@ describe('addTNodeAndUpdateInsertBeforeIndex', () => { it('should add first node', () => { const previousTNodes: TNode[] = []; - addTNodeAndUpdateInsertBeforeIndex(previousTNodes, tPlaceholderElementNode(20)); + addTNodeAndUpdateInsertBeforeIndex(previousTNodes, tPlaceholderElementNode(21)); expect(previousTNodes).toEqual([ - matchTNode({index: 20, insertBeforeIndex: null}), + matchTNode({index: 21, insertBeforeIndex: null}), ]); }); @@ -39,14 +39,14 @@ describe('addTNodeAndUpdateInsertBeforeIndex', () => { describe('whose index is greater than those already there', () => { it('should not update the `insertBeforeIndex` values', () => { const previousTNodes: TNode[] = [ - tPlaceholderElementNode(20), tPlaceholderElementNode(21), + tPlaceholderElementNode(22), ]; - addTNodeAndUpdateInsertBeforeIndex(previousTNodes, tPlaceholderElementNode(22)); + addTNodeAndUpdateInsertBeforeIndex(previousTNodes, tPlaceholderElementNode(23)); expect(previousTNodes).toEqual([ - matchTNode({index: 20, insertBeforeIndex: null}), matchTNode({index: 21, insertBeforeIndex: null}), matchTNode({index: 22, insertBeforeIndex: null}), + matchTNode({index: 23, insertBeforeIndex: null}), ]); }); }); @@ -54,44 +54,44 @@ describe('addTNodeAndUpdateInsertBeforeIndex', () => { describe('whose index is smaller than current nodes', () => { it('should update the previous insertBeforeIndex', () => { const previousTNodes: TNode[] = [ - tPlaceholderElementNode(21), tPlaceholderElementNode(22), + tPlaceholderElementNode(23), ]; - addTNodeAndUpdateInsertBeforeIndex(previousTNodes, tPlaceholderElementNode(20)); + addTNodeAndUpdateInsertBeforeIndex(previousTNodes, tPlaceholderElementNode(21)); expect(previousTNodes).toEqual([ - matchTNode({index: 21, insertBeforeIndex: 20}), - matchTNode({index: 22, insertBeforeIndex: 20}), - matchTNode({index: 20, insertBeforeIndex: null}), + matchTNode({index: 22, insertBeforeIndex: 21}), + matchTNode({index: 23, insertBeforeIndex: 21}), + matchTNode({index: 21, insertBeforeIndex: null}), ]); }); it('should not update the previous insertBeforeIndex if it is already set', () => { const previousTNodes: TNode[] = [ - tPlaceholderElementNode(22, 21), - tPlaceholderElementNode(23, 21), - tPlaceholderElementNode(21), + tPlaceholderElementNode(23, 22), + tPlaceholderElementNode(24, 22), + tPlaceholderElementNode(22), ]; - addTNodeAndUpdateInsertBeforeIndex(previousTNodes, tPlaceholderElementNode(20)); + addTNodeAndUpdateInsertBeforeIndex(previousTNodes, tPlaceholderElementNode(21)); expect(previousTNodes).toEqual([ + matchTNode({index: 23, insertBeforeIndex: 22}), + matchTNode({index: 24, insertBeforeIndex: 22}), matchTNode({index: 22, insertBeforeIndex: 21}), - matchTNode({index: 23, insertBeforeIndex: 21}), - matchTNode({index: 21, insertBeforeIndex: 20}), - matchTNode({index: 20, insertBeforeIndex: null}), + matchTNode({index: 21, insertBeforeIndex: null}), ]); }); it('should not update the previous insertBeforeIndex if it is created after', () => { const previousTNodes: TNode[] = [ - tPlaceholderElementNode(25, 20), - tPlaceholderElementNode(26, 20), - tPlaceholderElementNode(20), + tPlaceholderElementNode(26, 21), + tPlaceholderElementNode(27, 21), + tPlaceholderElementNode(21), ]; - addTNodeAndUpdateInsertBeforeIndex(previousTNodes, tPlaceholderElementNode(23)); + addTNodeAndUpdateInsertBeforeIndex(previousTNodes, tPlaceholderElementNode(24)); expect(previousTNodes).toEqual([ - matchTNode({index: 25, insertBeforeIndex: 20}), - matchTNode({index: 26, insertBeforeIndex: 20}), - matchTNode({index: 20, insertBeforeIndex: null}), - matchTNode({index: 23, insertBeforeIndex: null}), + matchTNode({index: 26, insertBeforeIndex: 21}), + matchTNode({index: 27, insertBeforeIndex: 21}), + matchTNode({index: 21, insertBeforeIndex: null}), + matchTNode({index: 24, insertBeforeIndex: null}), ]); }); }); @@ -101,14 +101,14 @@ describe('addTNodeAndUpdateInsertBeforeIndex', () => { describe('whose index is greater than those already there', () => { it('should not update the `insertBeforeIndex` values', () => { const previousTNodes: TNode[] = [ - tPlaceholderElementNode(20), tPlaceholderElementNode(21), + tPlaceholderElementNode(22), ]; - addTNodeAndUpdateInsertBeforeIndex(previousTNodes, tI18NTextNode(22)); + addTNodeAndUpdateInsertBeforeIndex(previousTNodes, tI18NTextNode(23)); expect(previousTNodes).toEqual([ - matchTNode({index: 20, insertBeforeIndex: 22}), - matchTNode({index: 21, insertBeforeIndex: 22}), - matchTNode({index: 22, insertBeforeIndex: null}), + matchTNode({index: 21, insertBeforeIndex: 23}), + matchTNode({index: 22, insertBeforeIndex: 23}), + matchTNode({index: 23, insertBeforeIndex: null}), ]); }); }); @@ -116,44 +116,44 @@ describe('addTNodeAndUpdateInsertBeforeIndex', () => { describe('whose index is smaller than current nodes', () => { it('should update the previous insertBeforeIndex', () => { const previousTNodes: TNode[] = [ - tPlaceholderElementNode(21), tPlaceholderElementNode(22), + tPlaceholderElementNode(23), ]; - addTNodeAndUpdateInsertBeforeIndex(previousTNodes, tI18NTextNode(20)); + addTNodeAndUpdateInsertBeforeIndex(previousTNodes, tI18NTextNode(21)); expect(previousTNodes).toEqual([ - matchTNode({index: 21, insertBeforeIndex: 20}), - matchTNode({index: 22, insertBeforeIndex: 20}), - matchTNode({index: 20, insertBeforeIndex: null}), + matchTNode({index: 22, insertBeforeIndex: 21}), + matchTNode({index: 23, insertBeforeIndex: 21}), + matchTNode({index: 21, insertBeforeIndex: null}), ]); }); it('should not update the previous insertBeforeIndex if it is already set', () => { const previousTNodes: TNode[] = [ - tPlaceholderElementNode(22, 21), - tPlaceholderElementNode(23, 21), - tPlaceholderElementNode(21), + tPlaceholderElementNode(23, 22), + tPlaceholderElementNode(24, 22), + tPlaceholderElementNode(22), ]; - addTNodeAndUpdateInsertBeforeIndex(previousTNodes, tI18NTextNode(20)); + addTNodeAndUpdateInsertBeforeIndex(previousTNodes, tI18NTextNode(21)); expect(previousTNodes).toEqual([ + matchTNode({index: 23, insertBeforeIndex: 22}), + matchTNode({index: 24, insertBeforeIndex: 22}), matchTNode({index: 22, insertBeforeIndex: 21}), - matchTNode({index: 23, insertBeforeIndex: 21}), - matchTNode({index: 21, insertBeforeIndex: 20}), - matchTNode({index: 20, insertBeforeIndex: null}), + matchTNode({index: 21, insertBeforeIndex: null}), ]); }); it('should not update the previous insertBeforeIndex if it is created after', () => { const previousTNodes: TNode[] = [ - tPlaceholderElementNode(25, 20), - tPlaceholderElementNode(26, 20), - tPlaceholderElementNode(20), + tPlaceholderElementNode(26, 21), + tPlaceholderElementNode(27, 21), + tPlaceholderElementNode(21), ]; - addTNodeAndUpdateInsertBeforeIndex(previousTNodes, tI18NTextNode(23)); + addTNodeAndUpdateInsertBeforeIndex(previousTNodes, tI18NTextNode(24)); expect(previousTNodes).toEqual([ - matchTNode({index: 25, insertBeforeIndex: 20}), - matchTNode({index: 26, insertBeforeIndex: 20}), - matchTNode({index: 20, insertBeforeIndex: 23}), - matchTNode({index: 23, insertBeforeIndex: null}), + matchTNode({index: 26, insertBeforeIndex: 21}), + matchTNode({index: 27, insertBeforeIndex: 21}), + matchTNode({index: 21, insertBeforeIndex: 24}), + matchTNode({index: 24, insertBeforeIndex: null}), ]); }); }); @@ -162,22 +162,22 @@ describe('addTNodeAndUpdateInsertBeforeIndex', () => { describe('scenario', () => { it('should rearrange the nodes', () => { const previousTNodes: TNode[] = []; - addTNodeAndUpdateInsertBeforeIndex(previousTNodes, tPlaceholderElementNode(22)); - addTNodeAndUpdateInsertBeforeIndex(previousTNodes, tPlaceholderElementNode(28)); - addTNodeAndUpdateInsertBeforeIndex(previousTNodes, tPlaceholderElementNode(24)); - addTNodeAndUpdateInsertBeforeIndex(previousTNodes, tPlaceholderElementNode(25)); - addTNodeAndUpdateInsertBeforeIndex(previousTNodes, tI18NTextNode(29)); addTNodeAndUpdateInsertBeforeIndex(previousTNodes, tPlaceholderElementNode(23)); - addTNodeAndUpdateInsertBeforeIndex(previousTNodes, tPlaceholderElementNode(27)); + addTNodeAndUpdateInsertBeforeIndex(previousTNodes, tPlaceholderElementNode(29)); + addTNodeAndUpdateInsertBeforeIndex(previousTNodes, tPlaceholderElementNode(25)); + addTNodeAndUpdateInsertBeforeIndex(previousTNodes, tPlaceholderElementNode(26)); + addTNodeAndUpdateInsertBeforeIndex(previousTNodes, tI18NTextNode(30)); + addTNodeAndUpdateInsertBeforeIndex(previousTNodes, tPlaceholderElementNode(24)); + addTNodeAndUpdateInsertBeforeIndex(previousTNodes, tPlaceholderElementNode(28)); expect(previousTNodes).toEqual([ - matchTNode({index: 22, insertBeforeIndex: 29}), - matchTNode({index: 28, insertBeforeIndex: 24}), - matchTNode({index: 24, insertBeforeIndex: 29}), - matchTNode({index: 25, insertBeforeIndex: 29}), - matchTNode({index: 29, insertBeforeIndex: null}), - matchTNode({index: 23, insertBeforeIndex: null}), - matchTNode({index: 27, insertBeforeIndex: null}), + matchTNode({index: 23, insertBeforeIndex: 30}), + matchTNode({index: 29, insertBeforeIndex: 25}), + matchTNode({index: 25, insertBeforeIndex: 30}), + matchTNode({index: 26, insertBeforeIndex: 30}), + matchTNode({index: 30, insertBeforeIndex: null}), + matchTNode({index: 24, insertBeforeIndex: null}), + matchTNode({index: 28, insertBeforeIndex: null}), ]); }); }); -}); \ No newline at end of file +}); diff --git a/packages/core/test/render3/i18n/i18n_parse_spec.ts b/packages/core/test/render3/i18n/i18n_parse_spec.ts index 339453a1b0134e..eb9cb5fce0639c 100644 --- a/packages/core/test/render3/i18n/i18n_parse_spec.ts +++ b/packages/core/test/render3/i18n/i18n_parse_spec.ts @@ -95,22 +95,22 @@ describe('i18n_parse', () => { fixture.apply(() => { applyCreateOpCodes(fixture.lView, tI18n.create, fixture.host, null); - expect(fixture.host.innerHTML).toEqual('before||after'); + expect(fixture.host.innerHTML).toEqual('before||after'); }); fixture.apply(() => { ɵɵi18nExp('A'); ɵɵi18nApply(0); // index 0 + HEADER_OFFSET = 20; - expect(fixture.host.innerHTML).toEqual('before|caseA|after'); + expect(fixture.host.innerHTML).toEqual('before|caseA|after'); }); fixture.apply(() => { ɵɵi18nExp('x'); ɵɵi18nApply(0); // index 0 + HEADER_OFFSET = 20; - expect(fixture.host.innerHTML).toEqual('before|otherCase|after'); + expect(fixture.host.innerHTML).toEqual('before|otherCase|after'); }); fixture.apply(() => { ɵɵi18nExp('A'); ɵɵi18nApply(0); // index 0 + HEADER_OFFSET = 20; - expect(fixture.host.innerHTML).toEqual('before|caseA|after'); + expect(fixture.host.innerHTML).toEqual('before|caseA|after'); }); }); @@ -122,23 +122,23 @@ describe('i18n_parse', () => { }`); fixture.apply(() => { applyCreateOpCodes(fixture.lView, tI18n.create, fixture.host, null); - expect(fixture.host.innerHTML).toEqual(''); + expect(fixture.host.innerHTML).toEqual(''); }); fixture.apply(() => { ɵɵi18nExp('A'); ɵɵi18nApply(0); // index 0 + HEADER_OFFSET = 20; - expect(fixture.host.innerHTML).toEqual('Hello world!'); + expect(fixture.host.innerHTML).toEqual('Hello world!'); }); fixture.apply(() => { ɵɵi18nExp('x'); ɵɵi18nApply(0); // index 0 + HEADER_OFFSET = 20; expect(fixture.host.innerHTML) - .toEqual('
nestedOther
'); + .toEqual('
nestedOther
'); }); fixture.apply(() => { ɵɵi18nExp('A'); ɵɵi18nApply(0); // index 0 + HEADER_OFFSET = 20; - expect(fixture.host.innerHTML).toEqual('Hello world!'); + expect(fixture.host.innerHTML).toEqual('Hello world!'); }); }); @@ -244,7 +244,7 @@ describe('i18n_parse', () => { fixture.apply(() => { applyCreateOpCodes(fixture.lView, tI18n.create, fixture.host, null); - expect(fixture.host.innerHTML).toEqual(''); + expect(fixture.host.innerHTML).toEqual(''); }); fixture.apply(() => { ɵɵi18nExp('A'); @@ -252,28 +252,28 @@ describe('i18n_parse', () => { ɵɵi18nExp('value1'); ɵɵi18nApply(0); // index 0 + HEADER_OFFSET = 20; expect(fixture.host.innerHTML) - .toEqual('parentA nested0!'); + .toEqual('parentA nested0!'); }); fixture.apply(() => { ɵɵi18nExp('A'); ɵɵi18nExp('x'); ɵɵi18nExp('value1'); ɵɵi18nApply(0); // index 0 + HEADER_OFFSET = 20; - expect(fixture.host.innerHTML).toEqual('parentA value1!'); + expect(fixture.host.innerHTML).toEqual('parentA value1!'); }); fixture.apply(() => { ɵɵi18nExp('x'); ɵɵi18nExp('x'); ɵɵi18nExp('value2'); ɵɵi18nApply(0); // index 0 + HEADER_OFFSET = 20; - expect(fixture.host.innerHTML).toEqual('parentOther'); + expect(fixture.host.innerHTML).toEqual('parentOther'); }); fixture.apply(() => { ɵɵi18nExp('A'); ɵɵi18nExp('A'); ɵɵi18nExp('value2'); ɵɵi18nApply(0); // index 0 + HEADER_OFFSET = 20; - expect(fixture.host.innerHTML).toEqual('parentA value2!'); + expect(fixture.host.innerHTML).toEqual('parentA value2!'); }); }); }); diff --git a/packages/core/test/render3/i18n/i18n_spec.ts b/packages/core/test/render3/i18n/i18n_spec.ts index 26c2be6c2ab646..61018cebb04e44 100644 --- a/packages/core/test/render3/i18n/i18n_spec.ts +++ b/packages/core/test/render3/i18n/i18n_spec.ts @@ -284,9 +284,9 @@ describe('Runtime i18n', () => { ]), matchDebug([ 'lView[31] = document.createTextNode("")', - '(lView[20] as Element).appendChild(lView[31])', + '(lView[21] as Element).appendChild(lView[31])', 'lView[32] = document.createElement("span")', - '(lView[20] as Element).appendChild(lView[32])', + '(lView[21] as Element).appendChild(lView[32])', 'lView[33] = document.createTextNode("emails")', '(lView[32] as Element).appendChild(lView[33])', ]), @@ -361,7 +361,7 @@ describe('Runtime i18n', () => { 'lView[26] = document.createComment("nested ICU 0")', `(lView[${HEADER_OFFSET + 0}] as Element).appendChild(lView[${HEADER_OFFSET + 6}])`, 'lView[31] = document.createTextNode("!")', - '(lView[20] as Element).appendChild(lView[31])', + '(lView[21] as Element).appendChild(lView[31])', ]), ], update: [ @@ -398,7 +398,7 @@ describe('Runtime i18n', () => { ]), matchDebug([ 'lView[30] = document.createTextNode("animals")', - '(lView[20] as Element).appendChild(lView[30])', + '(lView[21] as Element).appendChild(lView[30])', ]), ], update: [ @@ -428,7 +428,7 @@ describe('Runtime i18n', () => { }, undefined, nbConsts, HEADER_OFFSET + index); expect(opCodes).toEqual(matchDebug([ - 'if (mask & 0b1) { (lView[20] as Element).setAttribute(\'title\', `Hello ${lView[i-1]}!`); }', + 'if (mask & 0b1) { (lView[21] as Element).setAttribute(\'title\', `Hello ${lView[i-1]}!`); }', ])); }); @@ -444,7 +444,7 @@ describe('Runtime i18n', () => { }, undefined, nbConsts, HEADER_OFFSET + index); expect(opCodes).toEqual(matchDebug([ - 'if (mask & 0b11) { (lView[20] as Element).setAttribute(\'title\', `Hello ${lView[i-1]} and ${lView[i-2]}, again ${lView[i-1]}!`); }', + 'if (mask & 0b11) { (lView[21] as Element).setAttribute(\'title\', `Hello ${lView[i-1]} and ${lView[i-2]}, again ${lView[i-1]}!`); }', ])); }); @@ -460,8 +460,8 @@ describe('Runtime i18n', () => { }, undefined, nbConsts, HEADER_OFFSET + index); expect(opCodes).toEqual(matchDebug([ - 'if (mask & 0b1) { (lView[20] as Element).setAttribute(\'title\', `Hello ${lView[i-1]}!`); }', - 'if (mask & 0b1) { (lView[20] as Element).setAttribute(\'aria-label\', `Hello ${lView[i-1]}!`); }', + 'if (mask & 0b1) { (lView[21] as Element).setAttribute(\'title\', `Hello ${lView[i-1]}!`); }', + 'if (mask & 0b1) { (lView[21] as Element).setAttribute(\'aria-label\', `Hello ${lView[i-1]}!`); }', ])); }); }); diff --git a/packages/core/test/render3/integration_spec.ts b/packages/core/test/render3/integration_spec.ts index fdd830d44b8426..07976a2c01c187 100644 --- a/packages/core/test/render3/integration_spec.ts +++ b/packages/core/test/render3/integration_spec.ts @@ -6,6 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ +import {getLViewById} from '@angular/core/src/render3/instructions/shared'; import {RElement} from '@angular/core/src/render3/interfaces/renderer_dom'; import {RendererType2} from '../../src/render/api_flags'; import {getLContext, readPatchedData} from '../../src/render3/context_discovery'; @@ -399,14 +400,14 @@ describe('element discovery', () => { const section = fixture.hostElement.querySelector('section')!; const sectionContext = getLContext(section)!; - const sectionLView = sectionContext.lView!; + const sectionLView = getLViewById(sectionContext.lViewId)!; expect(sectionContext.nodeIndex).toEqual(HEADER_OFFSET); expect(sectionLView.length).toBeGreaterThan(HEADER_OFFSET); expect(sectionContext.native).toBe(section); const div = fixture.hostElement.querySelector('div')!; const divContext = getLContext(div)!; - const divLView = divContext.lView!; + const divLView = getLViewById(divContext.lViewId)!; expect(divContext.nodeIndex).toEqual(HEADER_OFFSET + 1); expect(divLView.length).toBeGreaterThan(HEADER_OFFSET); expect(divContext.native).toBe(div); @@ -608,9 +609,9 @@ describe('element discovery', () => { const shadowContext = getLContext(header)!; const projectedContext = getLContext(p)!; - const parentComponentData = parentContext.lView; - const shadowComponentData = shadowContext.lView; - const projectedComponentData = projectedContext.lView; + const parentComponentData = getLViewById(parentContext.lViewId); + const shadowComponentData = getLViewById(shadowContext.lViewId); + const projectedComponentData = getLViewById(projectedContext.lViewId); expect(projectedComponentData).toBe(parentComponentData); expect(shadowComponentData).not.toBe(parentComponentData); @@ -682,12 +683,12 @@ describe('element discovery', () => { expect(hostLView).toBe(componentLView); const context1 = getLContext(hostElm)!; - expect(context1.lView).toBe(hostLView); + expect(getLViewById(context1.lViewId)).toBe(hostLView); expect(context1.native).toEqual(hostElm); const context2 = getLContext(component)!; expect(context2).toBe(context1); - expect(context2.lView).toBe(hostLView); + expect(getLViewById(context2.lViewId)).toBe(hostLView); expect(context2.native).toEqual(hostElm); }); @@ -738,7 +739,7 @@ describe('element discovery', () => { const div1 = hostElm.querySelector('div:first-child')! as any; const div2 = hostElm.querySelector('div:last-child')! as any; const context = getLContext(hostElm)!; - const componentView = context.lView[context.nodeIndex]; + const componentView = getLViewById(context.lViewId)![context.nodeIndex]; expect(componentView).toContain(myDir1Instance); expect(componentView).toContain(myDir2Instance); @@ -752,9 +753,9 @@ describe('element discovery', () => { const d2Context = getLContext(myDir2Instance)!; const d3Context = getLContext(myDir3Instance)!; - expect(d1Context.lView).toEqual(componentView); - expect(d2Context.lView).toEqual(componentView); - expect(d3Context.lView).toEqual(componentView); + expect(getLViewById(d1Context.lViewId)).toEqual(componentView); + expect(getLViewById(d2Context.lViewId)).toEqual(componentView); + expect(getLViewById(d3Context.lViewId)).toEqual(componentView); expect(readPatchedData(myDir1Instance)).toBe(d1Context); expect(readPatchedData(myDir2Instance)).toBe(d2Context); @@ -917,16 +918,16 @@ describe('element discovery', () => { const context = getLContext(child)!; expect(readPatchedData(child)).toBeTruthy(); - const componentData = context.lView[context.nodeIndex]; + const componentData = getLViewById(context.lViewId)![context.nodeIndex]; const component = componentData[CONTEXT]; expect(component instanceof ChildComp).toBeTruthy(); - expect(readPatchedData(component)).toBe(context.lView); + expect(readPatchedData(component)).toBe(getLViewById(context.lViewId)); const componentContext = getLContext(component)!; expect(readPatchedData(component)).toBe(componentContext); expect(componentContext.nodeIndex).toEqual(context.nodeIndex); expect(componentContext.native).toEqual(context.native); - expect(componentContext.lView).toEqual(context.lView); + expect(getLViewById(componentContext.lViewId)).toEqual(getLViewById(context.lViewId)); }); }); diff --git a/packages/core/test/render3/perf/view_destroy_hook/index.ts b/packages/core/test/render3/perf/view_destroy_hook/index.ts index e96c76f9c02449..5e63841fd04742 100644 --- a/packages/core/test/render3/perf/view_destroy_hook/index.ts +++ b/packages/core/test/render3/perf/view_destroy_hook/index.ts @@ -8,11 +8,10 @@ import {OnDestroy} from '@angular/core'; import {ɵɵdefineDirective, ɵɵelement, ɵɵelementEnd, ɵɵelementStart} from '../../../../src/render3/index'; -import {createLView, createTNode, createTView} from '../../../../src/render3/instructions/shared'; +import {createLView, createTNode, createTView, destroyLView} from '../../../../src/render3/instructions/shared'; import {RenderFlags} from '../../../../src/render3/interfaces/definition'; import {TNodeType} from '../../../../src/render3/interfaces/node'; import {LViewFlags, TViewType} from '../../../../src/render3/interfaces/view'; -import {destroyLView} from '../../../../src/render3/node_manipulation'; import {createBenchmark} from '../micro_bench'; import {createAndRenderLView} from '../setup'; diff --git a/packages/core/test/render3/render_util.ts b/packages/core/test/render3/render_util.ts index 8537c7d5743e8a..c735e1d57ef9ee 100644 --- a/packages/core/test/render3/render_util.ts +++ b/packages/core/test/render3/render_util.ts @@ -13,7 +13,7 @@ import {ElementRef} from '@angular/core/src/linker/element_ref'; import {TemplateRef} from '@angular/core/src/linker/template_ref'; import {ViewContainerRef} from '@angular/core/src/linker/view_container_ref'; import {Renderer2} from '@angular/core/src/render/api'; -import {createLView, createTView, getOrCreateTComponentView, getOrCreateTNode, renderComponentOrTemplate} from '@angular/core/src/render3/instructions/shared'; +import {createLView, createTView, destroyLView, getOrCreateTComponentView, getOrCreateTNode, renderComponentOrTemplate} from '@angular/core/src/render3/instructions/shared'; import {TConstants, TNodeType} from '@angular/core/src/render3/interfaces/node'; import {RComment, RElement, RNode, RText} from '@angular/core/src/render3/interfaces/renderer_dom'; import {enterView, getLView} from '@angular/core/src/render3/state'; @@ -37,7 +37,6 @@ import {DirectiveDefList, DirectiveDefListOrFactory, DirectiveTypesOrFactory, Ho import {PlayerHandler} from '../../src/render3/interfaces/player'; import {domRendererFactory3, ProceduralRenderer3, Renderer3, RendererFactory3, RendererStyleFlags3} from '../../src/render3/interfaces/renderer'; import {LView, LViewFlags, TVIEW, TViewType} from '../../src/render3/interfaces/view'; -import {destroyLView} from '../../src/render3/node_manipulation'; import {getRootView} from '../../src/render3/util/view_traversal_utils'; import {Sanitizer} from '../../src/sanitization/sanitizer';