refactor(core): Remove circular dependency between `LContainer` and `ViewRef`. (#39621)

`LContainer` stores `ViewRef`s this is not quite right as it creates
circular dependency between the two types. Also `LContainer` should not
be aware of `ViewRef` which iv ViewEngine specific construct.

PR Close #39621
This commit is contained in:
Misko Hevery 2020-11-10 09:54:05 -08:00 committed by atscott
parent 621c34ddec
commit e6ae0c5349
8 changed files with 331 additions and 1978 deletions

File diff suppressed because it is too large Load Diff

View File

@ -222,7 +222,8 @@ const R3ViewContainerRef = class ViewContainerRef extends VE_ViewContainerRef {
} }
get(index: number): ViewRef|null { get(index: number): ViewRef|null {
return this._lContainer[VIEW_REFS] !== null && this._lContainer[VIEW_REFS]![index] || null; const viewRefs = getViewRefs(this._lContainer);
return viewRefs !== null && viewRefs[index] || null;
} }
get length(): number { get length(): number {
@ -265,8 +266,6 @@ const R3ViewContainerRef = class ViewContainerRef extends VE_ViewContainerRef {
throw new Error('Cannot insert a destroyed View in a ViewContainer!'); throw new Error('Cannot insert a destroyed View in a ViewContainer!');
} }
this.allocateContainerIfNeeded();
if (viewAttachedToContainer(lView)) { if (viewAttachedToContainer(lView)) {
// If view is already attached, detach it first so we clean up references appropriately. // If view is already attached, detach it first so we clean up references appropriately.
@ -309,7 +308,7 @@ const R3ViewContainerRef = class ViewContainerRef extends VE_ViewContainerRef {
} }
(viewRef as R3ViewRef<any>).attachToViewContainerRef(this); (viewRef as R3ViewRef<any>).attachToViewContainerRef(this);
addToArray(lContainer[VIEW_REFS]!, adjustedIdx, viewRef); addToArray(getOrCreateViewRefs(lContainer), adjustedIdx, viewRef);
return viewRef; return viewRef;
} }
@ -322,12 +321,11 @@ const R3ViewContainerRef = class ViewContainerRef extends VE_ViewContainerRef {
} }
indexOf(viewRef: ViewRef): number { indexOf(viewRef: ViewRef): number {
const viewRefsArr = this._lContainer[VIEW_REFS]; const viewRefsArr = getViewRefs(this._lContainer);
return viewRefsArr !== null ? viewRefsArr.indexOf(viewRef) : -1; return viewRefsArr !== null ? viewRefsArr.indexOf(viewRef) : -1;
} }
remove(index?: number): void { remove(index?: number): void {
this.allocateContainerIfNeeded();
const adjustedIdx = this._adjustIndex(index, -1); const adjustedIdx = this._adjustIndex(index, -1);
const detachedView = detachView(this._lContainer, adjustedIdx); const detachedView = detachView(this._lContainer, adjustedIdx);
@ -338,17 +336,17 @@ const R3ViewContainerRef = class ViewContainerRef extends VE_ViewContainerRef {
// rely on an accurate container length. // rely on an accurate container length.
// (e.g. a method on this view container being called by a child directive's OnDestroy // (e.g. a method on this view container being called by a child directive's OnDestroy
// lifecycle hook) // lifecycle hook)
removeFromArray(this._lContainer[VIEW_REFS]!, adjustedIdx); removeFromArray(getOrCreateViewRefs(this._lContainer), adjustedIdx);
destroyLView(detachedView[TVIEW], detachedView); destroyLView(detachedView[TVIEW], detachedView);
} }
} }
detach(index?: number): ViewRef|null { detach(index?: number): ViewRef|null {
this.allocateContainerIfNeeded();
const adjustedIdx = this._adjustIndex(index, -1); const adjustedIdx = this._adjustIndex(index, -1);
const view = detachView(this._lContainer, adjustedIdx); const view = detachView(this._lContainer, adjustedIdx);
const wasDetached = view && removeFromArray(this._lContainer[VIEW_REFS]!, adjustedIdx) != null; const wasDetached =
view && removeFromArray(getOrCreateViewRefs(this._lContainer), adjustedIdx) != null;
return wasDetached ? new R3ViewRef(view!) : null; return wasDetached ? new R3ViewRef(view!) : null;
} }
@ -363,14 +361,16 @@ const R3ViewContainerRef = class ViewContainerRef extends VE_ViewContainerRef {
} }
return index; return index;
} }
private allocateContainerIfNeeded(): void {
if (this._lContainer[VIEW_REFS] === null) {
this._lContainer[VIEW_REFS] = [];
}
}
}; };
function getViewRefs(lContainer: LContainer): ViewRef[]|null {
return lContainer[VIEW_REFS];
}
function getOrCreateViewRefs(lContainer: LContainer): ViewRef[] {
return lContainer[VIEW_REFS] || (lContainer[VIEW_REFS] = []);
}
/** /**
* Creates a ViewContainerRef and stores it on the injector. * Creates a ViewContainerRef and stores it on the injector.
* *

View File

@ -0,0 +1,85 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {assertDefined} from '../util/assert';
import {icuContainerIterate} from './i18n/i18n_tree_shaking';
import {CONTAINER_HEADER_OFFSET} from './interfaces/container';
import {TElementNode, TIcuContainerNode, TNode, TNodeType} from './interfaces/node';
import {RNode} from './interfaces/renderer';
import {isLContainer} from './interfaces/type_checks';
import {DECLARATION_COMPONENT_VIEW, LView, T_HOST, TVIEW, TView} from './interfaces/view';
import {assertTNodeType} from './node_assert';
import {getLViewParent} from './util/view_traversal_utils';
import {unwrapRNode} from './util/view_utils';
export function collectNativeNodes(
tView: TView, lView: LView, tNode: TNode|null, result: any[],
isProjection: boolean = false): any[] {
while (tNode !== null) {
ngDevMode &&
assertTNodeType(
tNode,
TNodeType.AnyRNode | TNodeType.AnyContainer | TNodeType.Projection | TNodeType.Icu);
const lNode = lView[tNode.index];
if (lNode !== null) {
result.push(unwrapRNode(lNode));
}
// A given lNode can represent either a native node or a LContainer (when it is a host of a
// ViewContainerRef). When we find a LContainer we need to descend into it to collect root nodes
// from the views in this container.
if (isLContainer(lNode)) {
for (let i = CONTAINER_HEADER_OFFSET; i < lNode.length; i++) {
const lViewInAContainer = lNode[i];
const lViewFirstChildTNode = lViewInAContainer[TVIEW].firstChild;
if (lViewFirstChildTNode !== null) {
collectNativeNodes(
lViewInAContainer[TVIEW], lViewInAContainer, lViewFirstChildTNode, result);
}
}
}
const tNodeType = tNode.type;
if (tNodeType & TNodeType.ElementContainer) {
collectNativeNodes(tView, lView, tNode.child, result);
} else if (tNodeType & TNodeType.Icu) {
const nextRNode = icuContainerIterate(tNode as TIcuContainerNode, lView);
let rNode: RNode|null;
while (rNode = nextRNode()) {
result.push(rNode);
}
} else if (tNodeType & TNodeType.Projection) {
const componentView = lView[DECLARATION_COMPONENT_VIEW];
const componentHost = componentView[T_HOST] as TElementNode;
const slotIdx = tNode.projection as number;
ngDevMode &&
assertDefined(
componentHost.projection,
'Components with projection nodes (<ng-content>) must have projection slots defined.');
const nodesInSlot = componentHost.projection![slotIdx];
if (Array.isArray(nodesInSlot)) {
result.push(...nodesInSlot);
} else {
const parentView = getLViewParent(componentView)!;
ngDevMode &&
assertDefined(
parentView,
'Component views should always have a parent view (component\'s host view)');
collectNativeNodes(parentView[TVIEW], parentView, nodesInSlot, result, true);
}
}
tNode = isProjection ? tNode.projectionNext : tNode.next;
}
return result;
}

View File

@ -6,8 +6,6 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {ViewRef} from '../../linker/view_ref';
import {TNode} from './node'; import {TNode} from './node';
import {RComment, RElement} from './renderer'; import {RComment, RElement} from './renderer';
import {HOST, LView, NEXT, PARENT, T_HOST, TRANSPLANTED_VIEWS_TO_REFRESH} from './view'; import {HOST, LView, NEXT, PARENT, T_HOST, TRANSPLANTED_VIEWS_TO_REFRESH} from './view';
@ -127,8 +125,11 @@ export interface LContainer extends Array<any> {
* Array of `ViewRef`s used by any `ViewContainerRef`s that point to this container. * Array of `ViewRef`s used by any `ViewContainerRef`s that point to this container.
* *
* This is lazily initialized by `ViewContainerRef` when the first view is inserted. * This is lazily initialized by `ViewContainerRef` when the first view is inserted.
*
* NOTE: This is stored as `any[]` because render3 should really not be aware of `ViewRef` and
* doing so creates circular dependency.
*/ */
[VIEW_REFS]: ViewRef[]|null; [VIEW_REFS]: any[]|null;
} }
// Note: This hack is necessary so we don't erroneously get a circular dependency // Note: This hack is necessary so we don't erroneously get a circular dependency

View File

@ -10,19 +10,10 @@ import {ApplicationRef} from '../application_ref';
import {ChangeDetectorRef as viewEngine_ChangeDetectorRef} from '../change_detection/change_detector_ref'; import {ChangeDetectorRef as viewEngine_ChangeDetectorRef} from '../change_detection/change_detector_ref';
import {ViewContainerRef as viewEngine_ViewContainerRef} from '../linker/view_container_ref'; import {ViewContainerRef as viewEngine_ViewContainerRef} from '../linker/view_container_ref';
import {EmbeddedViewRef as viewEngine_EmbeddedViewRef, InternalViewRef as viewEngine_InternalViewRef} from '../linker/view_ref'; import {EmbeddedViewRef as viewEngine_EmbeddedViewRef, InternalViewRef as viewEngine_InternalViewRef} from '../linker/view_ref';
import {assertDefined} from '../util/assert'; import {collectNativeNodes} from './collect_native_nodes';
import {icuContainerIterate} from './i18n/i18n_tree_shaking';
import {checkNoChangesInRootView, checkNoChangesInternal, detectChangesInRootView, detectChangesInternal, markViewDirty, storeCleanupWithContext} from './instructions/shared'; import {checkNoChangesInRootView, checkNoChangesInternal, detectChangesInRootView, detectChangesInternal, markViewDirty, storeCleanupWithContext} from './instructions/shared';
import {CONTAINER_HEADER_OFFSET} from './interfaces/container'; import {CONTEXT, FLAGS, LView, LViewFlags, TVIEW} from './interfaces/view';
import {TElementNode, TIcuContainerNode, TNode, TNodeType} from './interfaces/node';
import {RNode} from './interfaces/renderer';
import {isLContainer} from './interfaces/type_checks';
import {CONTEXT, DECLARATION_COMPONENT_VIEW, FLAGS, LView, LViewFlags, T_HOST, TVIEW, TView} from './interfaces/view';
import {assertTNodeType} from './node_assert';
import {destroyLView, renderDetachView} from './node_manipulation'; import {destroyLView, renderDetachView} from './node_manipulation';
import {getLViewParent} from './util/view_traversal_utils';
import {unwrapRNode} from './util/view_utils';
@ -319,67 +310,3 @@ export class RootViewRef<T> extends ViewRef<T> {
return null!; return null!;
} }
} }
function collectNativeNodes(
tView: TView, lView: LView, tNode: TNode|null, result: any[],
isProjection: boolean = false): any[] {
while (tNode !== null) {
ngDevMode &&
assertTNodeType(
tNode,
TNodeType.AnyRNode | TNodeType.AnyContainer | TNodeType.Projection | TNodeType.Icu);
const lNode = lView[tNode.index];
if (lNode !== null) {
result.push(unwrapRNode(lNode));
}
// A given lNode can represent either a native node or a LContainer (when it is a host of a
// ViewContainerRef). When we find a LContainer we need to descend into it to collect root nodes
// from the views in this container.
if (isLContainer(lNode)) {
for (let i = CONTAINER_HEADER_OFFSET; i < lNode.length; i++) {
const lViewInAContainer = lNode[i];
const lViewFirstChildTNode = lViewInAContainer[TVIEW].firstChild;
if (lViewFirstChildTNode !== null) {
collectNativeNodes(
lViewInAContainer[TVIEW], lViewInAContainer, lViewFirstChildTNode, result);
}
}
}
const tNodeType = tNode.type;
if (tNodeType & TNodeType.ElementContainer) {
collectNativeNodes(tView, lView, tNode.child, result);
} else if (tNodeType & TNodeType.Icu) {
const nextRNode = icuContainerIterate(tNode as TIcuContainerNode, lView);
let rNode: RNode|null;
while (rNode = nextRNode()) {
result.push(rNode);
}
} else if (tNodeType & TNodeType.Projection) {
const componentView = lView[DECLARATION_COMPONENT_VIEW];
const componentHost = componentView[T_HOST] as TElementNode;
const slotIdx = tNode.projection as number;
ngDevMode &&
assertDefined(
componentHost.projection,
'Components with projection nodes (<ng-content>) must have projection slots defined.');
const nodesInSlot = componentHost.projection![slotIdx];
if (Array.isArray(nodesInSlot)) {
result.push(...nodesInSlot);
} else {
const parentView = getLViewParent(componentView)!;
ngDevMode &&
assertDefined(
parentView,
'Component views should always have a parent view (component\'s host view)');
collectNativeNodes(parentView[TVIEW], parentView, nodesInSlot, result, true);
}
}
tNode = isProjection ? tNode.projectionNext : tNode.next;
}
return result;
}

View File

@ -1055,6 +1055,9 @@
{ {
"name": "getOrCreateTNode" "name": "getOrCreateTNode"
}, },
{
"name": "getOrCreateViewRefs"
},
{ {
"name": "getOriginalError" "name": "getOriginalError"
}, },
@ -1106,6 +1109,9 @@
{ {
"name": "getTView" "name": "getTView"
}, },
{
"name": "getViewRefs"
},
{ {
"name": "handleError" "name": "handleError"
}, },

View File

@ -1370,6 +1370,9 @@
{ {
"name": "getOrCreateTNode" "name": "getOrCreateTNode"
}, },
{
"name": "getOrCreateViewRefs"
},
{ {
"name": "getOriginalError" "name": "getOriginalError"
}, },
@ -1439,6 +1442,9 @@
{ {
"name": "getToken" "name": "getToken"
}, },
{
"name": "getViewRefs"
},
{ {
"name": "handleError" "name": "handleError"
}, },

View File

@ -410,6 +410,9 @@
{ {
"name": "getOrCreateTNode" "name": "getOrCreateTNode"
}, },
{
"name": "getOrCreateViewRefs"
},
{ {
"name": "getOriginalError" "name": "getOriginalError"
}, },
@ -446,6 +449,9 @@
{ {
"name": "getTView" "name": "getTView"
}, },
{
"name": "getViewRefs"
},
{ {
"name": "handleError" "name": "handleError"
}, },