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 {
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 {
@ -265,8 +266,6 @@ const R3ViewContainerRef = class ViewContainerRef extends VE_ViewContainerRef {
throw new Error('Cannot insert a destroyed View in a ViewContainer!');
}
this.allocateContainerIfNeeded();
if (viewAttachedToContainer(lView)) {
// 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);
addToArray(lContainer[VIEW_REFS]!, adjustedIdx, viewRef);
addToArray(getOrCreateViewRefs(lContainer), adjustedIdx, viewRef);
return viewRef;
}
@ -322,12 +321,11 @@ const R3ViewContainerRef = class ViewContainerRef extends VE_ViewContainerRef {
}
indexOf(viewRef: ViewRef): number {
const viewRefsArr = this._lContainer[VIEW_REFS];
const viewRefsArr = getViewRefs(this._lContainer);
return viewRefsArr !== null ? viewRefsArr.indexOf(viewRef) : -1;
}
remove(index?: number): void {
this.allocateContainerIfNeeded();
const adjustedIdx = this._adjustIndex(index, -1);
const detachedView = detachView(this._lContainer, adjustedIdx);
@ -338,17 +336,17 @@ const R3ViewContainerRef = class ViewContainerRef extends VE_ViewContainerRef {
// rely on an accurate container length.
// (e.g. a method on this view container being called by a child directive's OnDestroy
// lifecycle hook)
removeFromArray(this._lContainer[VIEW_REFS]!, adjustedIdx);
removeFromArray(getOrCreateViewRefs(this._lContainer), adjustedIdx);
destroyLView(detachedView[TVIEW], detachedView);
}
}
detach(index?: number): ViewRef|null {
this.allocateContainerIfNeeded();
const adjustedIdx = this._adjustIndex(index, -1);
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;
}
@ -363,14 +361,16 @@ const R3ViewContainerRef = class ViewContainerRef extends VE_ViewContainerRef {
}
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.
*

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
*/
import {ViewRef} from '../../linker/view_ref';
import {TNode} from './node';
import {RComment, RElement} from './renderer';
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.
*
* 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

View File

@ -10,19 +10,10 @@ import {ApplicationRef} from '../application_ref';
import {ChangeDetectorRef as viewEngine_ChangeDetectorRef} from '../change_detection/change_detector_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 {assertDefined} from '../util/assert';
import {icuContainerIterate} from './i18n/i18n_tree_shaking';
import {collectNativeNodes} from './collect_native_nodes';
import {checkNoChangesInRootView, checkNoChangesInternal, detectChangesInRootView, detectChangesInternal, markViewDirty, storeCleanupWithContext} from './instructions/shared';
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 {CONTEXT, DECLARATION_COMPONENT_VIEW, FLAGS, LView, LViewFlags, T_HOST, TVIEW, TView} from './interfaces/view';
import {assertTNodeType} from './node_assert';
import {CONTEXT, FLAGS, LView, LViewFlags, TVIEW} from './interfaces/view';
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!;
}
}
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": "getOrCreateViewRefs"
},
{
"name": "getOriginalError"
},
@ -1106,6 +1109,9 @@
{
"name": "getTView"
},
{
"name": "getViewRefs"
},
{
"name": "handleError"
},

View File

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

View File

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