angular/packages/core/src/view/view_attach.ts

154 lines
5.5 KiB
TypeScript

/**
* @license
* Copyright Google Inc. 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 {ElementData, NodeDef, NodeFlags, Services, ViewData, ViewDefinition, ViewState} from './types';
import {RenderNodeAction, declaredViewContainer, isComponentView, renderNode, visitRootRenderNodes} from './util';
export function attachEmbeddedView(
parentView: ViewData, elementData: ElementData, viewIndex: number | undefined | null,
view: ViewData) {
let embeddedViews = elementData.viewContainer !._embeddedViews;
if (viewIndex === null || viewIndex === undefined) {
viewIndex = embeddedViews.length;
}
view.viewContainerParent = parentView;
addToArray(embeddedViews, viewIndex !, view);
attachProjectedView(elementData, view);
Services.dirtyParentQueries(view);
const prevView = viewIndex ! > 0 ? embeddedViews[viewIndex ! - 1] : null;
renderAttachEmbeddedView(elementData, prevView, view);
}
function attachProjectedView(vcElementData: ElementData, view: ViewData) {
const dvcElementData = declaredViewContainer(view);
if (!dvcElementData || dvcElementData === vcElementData ||
view.state & ViewState.IsProjectedView) {
return;
}
// Note: For performance reasons, we
// - add a view to template._projectedViews only 1x throughout its lifetime,
// and remove it not until the view is destroyed.
// (hard, as when a parent view is attached/detached we would need to attach/detach all
// nested projected views as well, even across component boundaries).
// - don't track the insertion order of views in the projected views array
// (hard, as when the views of the same template are inserted different view containers)
view.state |= ViewState.IsProjectedView;
let projectedViews = dvcElementData.template._projectedViews;
if (!projectedViews) {
projectedViews = dvcElementData.template._projectedViews = [];
}
projectedViews.push(view);
// Note: we are changing the NodeDef here as we cannot calculate
// the fact whether a template is used for projection during compilation.
markNodeAsProjectedTemplate(view.parent !.def, view.parentNodeDef !);
}
function markNodeAsProjectedTemplate(viewDef: ViewDefinition, nodeDef: NodeDef) {
if (nodeDef.flags & NodeFlags.ProjectedTemplate) {
return;
}
viewDef.nodeFlags |= NodeFlags.ProjectedTemplate;
nodeDef.flags |= NodeFlags.ProjectedTemplate;
let parentNodeDef = nodeDef.parent;
while (parentNodeDef) {
parentNodeDef.childFlags |= NodeFlags.ProjectedTemplate;
parentNodeDef = parentNodeDef.parent;
}
}
export function detachEmbeddedView(elementData: ElementData, viewIndex?: number): ViewData|null {
const embeddedViews = elementData.viewContainer !._embeddedViews;
if (viewIndex == null || viewIndex >= embeddedViews.length) {
viewIndex = embeddedViews.length - 1;
}
if (viewIndex < 0) {
return null;
}
const view = embeddedViews[viewIndex];
view.viewContainerParent = null;
removeFromArray(embeddedViews, viewIndex);
// See attachProjectedView for why we don't update projectedViews here.
Services.dirtyParentQueries(view);
renderDetachView(view);
return view;
}
export function detachProjectedView(view: ViewData) {
if (!(view.state & ViewState.IsProjectedView)) {
return;
}
const dvcElementData = declaredViewContainer(view);
if (dvcElementData) {
const projectedViews = dvcElementData.template._projectedViews;
if (projectedViews) {
removeFromArray(projectedViews, projectedViews.indexOf(view));
Services.dirtyParentQueries(view);
}
}
}
export function moveEmbeddedView(
elementData: ElementData, oldViewIndex: number, newViewIndex: number): ViewData {
const embeddedViews = elementData.viewContainer !._embeddedViews;
const view = embeddedViews[oldViewIndex];
removeFromArray(embeddedViews, oldViewIndex);
if (newViewIndex == null) {
newViewIndex = embeddedViews.length;
}
addToArray(embeddedViews, newViewIndex, view);
// Note: Don't need to change projectedViews as the order in there
// as always invalid...
Services.dirtyParentQueries(view);
renderDetachView(view);
const prevView = newViewIndex > 0 ? embeddedViews[newViewIndex - 1] : null;
renderAttachEmbeddedView(elementData, prevView, view);
return view;
}
function renderAttachEmbeddedView(
elementData: ElementData, prevView: ViewData | null, view: ViewData) {
const prevRenderNode = prevView ? renderNode(prevView, prevView.def.lastRenderRootNode !) :
elementData.renderElement;
const parentNode = view.renderer.parentNode(prevRenderNode);
const nextSibling = view.renderer.nextSibling(prevRenderNode);
// Note: We can't check if `nextSibling` is present, as on WebWorkers it will always be!
// However, browsers automatically do `appendChild` when there is no `nextSibling`.
visitRootRenderNodes(view, RenderNodeAction.InsertBefore, parentNode, nextSibling, undefined);
}
export function renderDetachView(view: ViewData) {
visitRootRenderNodes(view, RenderNodeAction.RemoveChild, null, null, undefined);
}
function addToArray(arr: any[], index: number, value: any) {
// perf: array.push is faster than array.splice!
if (index >= arr.length) {
arr.push(value);
} else {
arr.splice(index, 0, value);
}
}
function removeFromArray(arr: any[], index: number) {
// perf: array.pop is faster than array.splice!
if (index >= arr.length - 1) {
arr.pop();
} else {
arr.splice(index, 1);
}
}