refactor(ivy): insert embedded views immediately (#24629)

PR Close #24629
This commit is contained in:
Marc Laval 2018-06-22 15:37:38 +02:00 committed by Miško Hevery
parent 6e20e0aac8
commit f229449c67
7 changed files with 171 additions and 81 deletions

View File

@ -19,16 +19,16 @@ import {EmbeddedViewRef as viewEngine_EmbeddedViewRef, ViewRef as viewEngine_Vie
import {Type} from '../type';
import {assertDefined, assertGreaterThan, assertLessThan} from './assert';
import {addToViewTree, assertPreviousIsParent, createLContainer, createLNodeObject, createTNode, getDirectiveInstance, getPreviousOrParentNode, getRenderer, isComponent, renderEmbeddedTemplate, resolveDirective} from './instructions';
import {addToViewTree, assertPreviousIsParent, createEmbeddedViewNode, createLContainer, createLNodeObject, createTNode, getDirectiveInstance, getPreviousOrParentNode, getRenderer, isComponent, renderEmbeddedTemplate, resolveDirective} from './instructions';
import {VIEWS} from './interfaces/container';
import {ComponentTemplate, DirectiveDefInternal} from './interfaces/definition';
import {ComponentTemplate, DirectiveDefInternal, RenderFlags} from './interfaces/definition';
import {LInjector} from './interfaces/injector';
import {AttributeMarker, LContainerNode, LElementNode, LNode, LViewNode, TNodeFlags, TNodeType} from './interfaces/node';
import {LQueries, QueryReadType} from './interfaces/query';
import {Renderer3} from './interfaces/renderer';
import {DIRECTIVES, HOST_NODE, INJECTOR, LViewData, QUERIES, RENDERER, TVIEW, TView} from './interfaces/view';
import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert';
import {appendChild, detachView, getParentLNode, insertView, removeView} from './node_manipulation';
import {addRemoveViewFromContainer, appendChild, detachView, getChildLNode, getParentLNode, insertView, removeView} from './node_manipulation';
import {notImplemented, stringify} from './util';
import {EmbeddedViewRef, ViewRef} from './view_ref';
@ -631,8 +631,11 @@ class ViewContainerRef implements viewEngine_ViewContainerRef {
createEmbeddedView<C>(templateRef: viewEngine_TemplateRef<C>, context?: C, index?: number):
viewEngine_EmbeddedViewRef<C> {
const viewRef = templateRef.createEmbeddedView(context || <any>{});
this.insert(viewRef, index);
const adjustedIdx = this._adjustIndex(index);
const viewRef = (templateRef as TemplateRef<C>)
.createEmbeddedView(context || <any>{}, this._lContainerNode, adjustedIdx);
(viewRef as EmbeddedViewRef<any>).attachToViewContainerRef(this);
this._viewRefs.splice(adjustedIdx, 0, viewRef);
return viewRef;
}
@ -650,11 +653,15 @@ class ViewContainerRef implements viewEngine_ViewContainerRef {
const lViewNode = (viewRef as EmbeddedViewRef<any>)._lViewNode;
const adjustedIdx = this._adjustIndex(index);
(viewRef as EmbeddedViewRef<any>).attachToViewContainerRef(this);
insertView(this._lContainerNode, lViewNode, adjustedIdx);
lViewNode.dynamicParent = this._lContainerNode;
insertView(this._lContainerNode, lViewNode, adjustedIdx);
const views = this._lContainerNode.data[VIEWS];
const beforeNode = adjustedIdx + 1 < views.length ?
(getChildLNode(views[adjustedIdx + 1]) !).native :
this._lContainerNode.native;
addRemoveViewFromContainer(this._lContainerNode, lViewNode, true, beforeNode);
(viewRef as EmbeddedViewRef<any>).attachToViewContainerRef(this);
this._viewRefs.splice(adjustedIdx, 0, viewRef);
return viewRef;
@ -724,9 +731,14 @@ class TemplateRef<T> implements viewEngine_TemplateRef<T> {
this.elementRef = elementRef;
}
createEmbeddedView(context: T): viewEngine_EmbeddedViewRef<T> {
const viewNode =
renderEmbeddedTemplate(null, this._tView, context, this._renderer, this._queries);
createEmbeddedView(context: T, containerNode?: LContainerNode, index?: number):
viewEngine_EmbeddedViewRef<T> {
const viewNode = createEmbeddedViewNode(this._tView, context, this._renderer, this._queries);
if (containerNode) {
viewNode.dynamicParent = containerNode;
insertView(containerNode, viewNode, index !);
}
renderEmbeddedTemplate(viewNode, this._tView, context, RenderFlags.Create);
return new EmbeddedViewRef(viewNode, this._tView.template !as ComponentTemplate<T>, context);
}
}

View File

@ -8,6 +8,7 @@
import {assertEqual, assertLessThan} from './assert';
import {NO_CHANGE, bindingUpdated, createLNode, getPreviousOrParentNode, getRenderer, getViewData, load} from './instructions';
import {RENDER_PARENT} from './interfaces/container';
import {LContainerNode, LElementNode, LNode, TNodeType} from './interfaces/node';
import {BINDING_INDEX} from './interfaces/view';
import {appendChild, createTextNode, getParentLNode, removeChild} from './node_manipulation';
@ -320,6 +321,7 @@ export function i18nApply(startIndex: number, instructions: I18nInstruction[]):
if (removedNode.tNode.type === TNodeType.Container && removedNode.dynamicLContainerNode) {
removeChild(parentNode, removedNode.dynamicLContainerNode.native || null, viewData);
removedNode.dynamicLContainerNode.tNode.detached = true;
removedNode.dynamicLContainerNode.data[RENDER_PARENT] = null;
}
break;
}

View File

@ -6,19 +6,22 @@
* found in the LICENSE file at https://angular.io/license
*/
import './ng_dev_mode';
import {Sanitizer} from '../sanitization/security';
import {assertDefined, assertEqual, assertLessThan, assertNotDefined, assertNotEqual} from './assert';
import {throwCyclicDependencyError, throwErrorIfNoChangesMode, throwMultipleComponentError} from './errors';
import {executeHooks, executeInitHooks, queueInitHooks, queueLifecycleHooks} from './hooks';
import {ACTIVE_INDEX, LContainer, RENDER_PARENT, VIEWS} from './interfaces/container';
import {LInjector} from './interfaces/injector';
import {AttributeMarker, InitialInputData, InitialInputs, LContainerNode, LElementNode, LNode, LProjectionNode, LTextNode, LViewNode, PropertyAliases, PropertyAliasValue, TAttributes, TContainerNode, TElementNode, TNode, TNodeFlags, TNodeType,} from './interfaces/node';
import {CssSelectorList, LProjection, NG_PROJECT_AS_ATTR_NAME} from './interfaces/projection';
import {LQueries} from './interfaces/query';
import {BINDING_INDEX, CLEANUP, CONTEXT, CurrentMatchesList, DIRECTIVES, FLAGS, HEADER_OFFSET, HOST_NODE, INJECTOR, LViewData, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, RootContext, SANITIZER, TAIL, TVIEW, TView,} from './interfaces/view';
import './ng_dev_mode';
import {assertNodeType} from './node_assert';
import {appendChild, appendProjectedNode, canInsertNativeNode, createTextNode, getChildLNode, getLViewChild, getNextLNode, getParentLNode, insertView, removeChild, removeView} from './node_manipulation';
import {BINDING_INDEX, CLEANUP, CONTEXT, CurrentMatchesList, DIRECTIVES, FLAGS, HEADER_OFFSET, HOST_NODE, INJECTOR, LViewData, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, RootContext, SANITIZER, TAIL, TData, TVIEW, TView} from './interfaces/view';
import {AttributeMarker, TAttributes, LContainerNode, LElementNode, LNode, TNodeType, TNodeFlags, LProjectionNode, LTextNode, LViewNode, TNode, TContainerNode, InitialInputData, InitialInputs, PropertyAliases, PropertyAliasValue, TElementNode,} from './interfaces/node';
import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert';
import {appendChild, insertView, appendProjectedNode, removeView, canInsertNativeNode, createTextNode, getNextLNode, getChildLNode, getParentLNode, getLViewChild} from './node_manipulation';
import {isNodeMatchingSelectorList, matchingSelectorIndex} from './node_selector_matcher';
import {ComponentDefInternal, ComponentTemplate, ComponentQuery, DirectiveDefInternal, DirectiveDefListOrFactory, PipeDefListOrFactory, RenderFlags} from './interfaces/definition';
import {RComment, RElement, RText, Renderer3, RendererFactory3, ProceduralRenderer3, RendererStyleFlags3, isProceduralRenderer} from './interfaces/renderer';
@ -463,6 +466,30 @@ export function renderTemplate<T>(
return host;
}
/**
* Used for creating the LViewNode of a dynamic embedded view,
* either through ViewContainerRef.createEmbeddedView() or TemplateRef.createEmbeddedView().
* Such lViewNode will then be renderer with renderEmbeddedTemplate() (see below).
*/
export function createEmbeddedViewNode<T>(
tView: TView, context: T, renderer: Renderer3, queries?: LQueries | null): LViewNode {
const _isParent = isParent;
const _previousOrParentNode = previousOrParentNode;
isParent = true;
previousOrParentNode = null !;
const lView =
createLViewData(renderer, tView, context, LViewFlags.CheckAlways, getCurrentSanitizer());
if (queries) {
lView[QUERIES] = queries.createView();
}
const viewNode = createLNode(-1, TNodeType.View, null, null, null, lView);
isParent = _isParent;
previousOrParentNode = _previousOrParentNode;
return viewNode;
}
/**
* Used for rendering embedded views (e.g. dynamically created views)
*
@ -474,27 +501,14 @@ export function renderTemplate<T>(
* TView for dynamically created views on their host TNode, which only has one instance.
*/
export function renderEmbeddedTemplate<T>(
viewNode: LViewNode | null, tView: TView, context: T, renderer: Renderer3,
queries?: LQueries | null): LViewNode {
viewNode: LViewNode, tView: TView, context: T, rf: RenderFlags): LViewNode {
const _isParent = isParent;
const _previousOrParentNode = previousOrParentNode;
let oldView: LViewData;
let rf: RenderFlags = RenderFlags.Update;
try {
isParent = true;
previousOrParentNode = null !;
if (viewNode == null) {
const lView =
createLViewData(renderer, tView, context, LViewFlags.CheckAlways, getCurrentSanitizer());
if (queries) {
lView[QUERIES] = queries.createView();
}
viewNode = createLNode(-1, TNodeType.View, null, null, null, lView);
rf = RenderFlags.Create;
}
oldView = enterView(viewNode.data, viewNode);
namespaceHTML();
tView.template !(rf, context);
@ -1575,14 +1589,19 @@ function generateInitialInputs(
export function createLContainer(
parentLNode: LNode, currentView: LViewData, isForViewContainerRef?: boolean): LContainer {
ngDevMode && assertDefined(parentLNode, 'containers should have a parent');
let renderParent = canInsertNativeNode(parentLNode, currentView) ?
parentLNode as LElementNode | LViewNode :
null;
if (renderParent && renderParent.tNode.type === TNodeType.View) {
renderParent = getParentLNode(renderParent as LViewNode) !.data[RENDER_PARENT];
}
return [
isForViewContainerRef ? null : 0, // active index
currentView, // parent
null, // next
null, // queries
[], // views
canInsertNativeNode(parentLNode, currentView) ? parentLNode as LElementNode :
null // renderParent
renderParent as LElementNode
];
}
@ -1697,7 +1716,7 @@ function refreshDynamicEmbeddedViews(lViewData: LViewData) {
const dynamicViewData = lViewNode.data;
ngDevMode && assertDefined(dynamicViewData[TVIEW], 'TView must be allocated');
renderEmbeddedTemplate(
lViewNode, dynamicViewData[TVIEW], dynamicViewData[CONTEXT] !, renderer);
lViewNode, dynamicViewData[TVIEW], dynamicViewData[CONTEXT] !, RenderFlags.Update);
}
}
}
@ -1764,6 +1783,17 @@ export function embeddedViewStart(viewBlockId: number): RenderFlags {
enterView(
newView, viewNode = createLNode(viewBlockId, TNodeType.View, null, null, null, newView));
}
const containerNode = getParentLNode(viewNode) as LContainerNode;
if (containerNode) {
ngDevMode && assertNodeType(viewNode, TNodeType.View);
ngDevMode && assertNodeType(containerNode, TNodeType.Container);
const lContainer = containerNode.data;
if (creationMode) {
// it is a new view, insert it into collection of views for a given container
insertView(containerNode, viewNode, lContainer[ACTIVE_INDEX] !);
}
lContainer[ACTIVE_INDEX] !++;
}
return getRenderFlags(viewNode.data);
}
@ -1794,22 +1824,17 @@ function getOrCreateEmbeddedTView(viewIndex: number, parent: LContainerNode): TV
export function embeddedViewEnd(): void {
refreshView();
isParent = false;
const viewNode = previousOrParentNode = viewData[HOST_NODE] as LViewNode;
previousOrParentNode = viewData[HOST_NODE] as LViewNode;
if (creationMode) {
const containerNode = getParentLNode(previousOrParentNode) as LContainerNode;
if (containerNode) {
ngDevMode && assertNodeType(viewNode, TNodeType.View);
ngDevMode && assertNodeType(previousOrParentNode, TNodeType.View);
ngDevMode && assertNodeType(containerNode, TNodeType.Container);
const lContainer = containerNode.data;
if (creationMode) {
// When projected nodes are going to be inserted, the renderParent of the dynamic container
// used by the ViewContainerRef must be set.
setRenderParentInProjectedNodes(lContainer[RENDER_PARENT], viewNode);
// it is a new view, insert it into collection of views for a given container
insertView(containerNode, viewNode, lContainer[ACTIVE_INDEX] !);
setRenderParentInProjectedNodes(
containerNode.data[RENDER_PARENT], previousOrParentNode as LViewNode);
}
lContainer[ACTIVE_INDEX] !++;
}
leaveView(viewData[PARENT] !);
ngDevMode && assertEqual(isParent, false, 'isParent');
@ -2002,7 +2027,7 @@ export function projection(
const currentParent = getParentLNode(node);
if (canInsertNativeNode(currentParent, viewData)) {
ngDevMode && assertNodeType(currentParent, TNodeType.Element);
ngDevMode && assertNodeOfPossibleTypes(currentParent, TNodeType.Element, TNodeType.View);
// process each node in the list of projected nodes:
let nodeToProject: LNode|null = node.data.head;
const lastNodeToProject = node.data.tail;

View File

@ -12,7 +12,7 @@ import {LContainerNode, LElementNode, LNode, LProjectionNode, LTextNode, LViewNo
import {unusedValueExportToPlacateAjd as unused3} from './interfaces/projection';
import {ProceduralRenderer3, RComment, RElement, RNode, RText, Renderer3, isProceduralRenderer, unusedValueExportToPlacateAjd as unused4} from './interfaces/renderer';
import {CLEANUP, DIRECTIVES, FLAGS, HEADER_OFFSET, HOST_NODE, HookData, LViewData, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, TVIEW, unusedValueExportToPlacateAjd as unused5} from './interfaces/view';
import {assertNodeType} from './node_assert';
import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert';
import {stringify} from './util';
const unusedValueToPlacateAjd = unused1 + unused2 + unused3 + unused4 + unused5;
@ -313,16 +313,6 @@ export function insertView(
lView[QUERIES] !.insertView(index);
}
// If the container's renderParent is null, we know that it is a root node of its own parent view
// and we should wait until that parent processes its nodes (otherwise, we will insert this view's
// nodes twice - once now and once when its parent inserts its views).
if (container.data[RENDER_PARENT] !== null && !container.tNode.detached) {
// Find the node to insert in front of
const beforeNode =
index + 1 < views.length ? (getChildLNode(views[index + 1]) !).native : container.native;
addRemoveViewFromContainer(container, viewNode, true, beforeNode);
}
// Sets the attached flag
viewNode.data[FLAGS] |= LViewFlags.Attached;
@ -485,27 +475,66 @@ function executePipeOnDestroys(viewData: LViewData): void {
}
/**
* Returns whether a native element should be inserted in the given parent.
* Returns whether a native element can be inserted into the given parent.
*
* The native node can be inserted when its parent is:
* - A regular element => Yes
* - A component host element =>
* - if the `currentView` === the parent `view`: The element is in the content (vs the
* template)
* => don't add as the parent component will project if needed.
* - `currentView` !== the parent `view` => The element is in the template (vs the content),
* add it
* - View element => delay insertion, will be done on `viewEnd()`
* There are two reasons why we may not be able to insert a element immediately.
* - Projection: When creating a child content element of a component, we have to skip the
* insertion because the content of a component will be projected.
* `<component><content>delayed due to projection</content></component>`
* - Parent container is disconnected: This can happen when we are inserting a view into
* parent container, which itself is disconnected. For example the parent container is part
* of a View which has not be inserted or is mare for projection but has not been inserted
* into destination.
*
* @param parent The parent in which to insert the child
* @param currentView The LView being processed
* @return boolean Whether the child element should be inserted.
*
* @param parent The parent where the child will be inserted into.
* @param currentView Current LView being processed.
* @return boolean Whether the child should be inserted now (or delayed until later).
*/
export function canInsertNativeNode(parent: LNode, currentView: LViewData): boolean {
const parentIsElement = parent.tNode.type === TNodeType.Element;
// We can only insert into a Component or View. Any other type should be an Error.
ngDevMode && assertNodeOfPossibleTypes(parent, TNodeType.Element, TNodeType.View);
return parentIsElement &&
(parent.view !== currentView || parent.data === null /* Regular Element. */);
if (parent.tNode.type === TNodeType.Element) {
// Parent is an element.
if (parent.view !== currentView) {
// If the Parent view is not the same as current view than we are inserting across
// Views. This happens when we insert a root element of the component view into
// the component host element and it should always be eager.
return true;
}
// Parent elements can be a component which may have projection.
if (parent.data === null) {
// Parent is a regular non-component element. We should eagerly insert into it
// since we know that this relationship will never be broken.
return true;
} else {
// Parent is a Component. Component's content nodes are not inserted immediately
// because they will be projected, and so doing insert at this point would be wasteful.
// Since the projection would than move it to its final destination.
return false;
}
} else {
// Parent is a View.
ngDevMode && assertNodeType(parent, TNodeType.View);
// Because we are inserting into a `View` the `View` may be disconnected.
const grandParentContainer = getParentLNode(parent) as LContainerNode;
if (grandParentContainer == null) {
// The `View` is not inserted into a `Container` we have to delay insertion.
return false;
}
ngDevMode && assertNodeType(grandParentContainer, TNodeType.Container);
if (grandParentContainer.data[RENDER_PARENT] == null) {
// The parent `Container` itself is disconnected. So we have to delay.
return false;
} else {
// The parent `Container` is in inserted state, so we can eagerly insert into
// this location.
return true;
}
}
}
/**
@ -520,10 +549,21 @@ export function canInsertNativeNode(parent: LNode, currentView: LViewData): bool
*/
export function appendChild(parent: LNode, child: RNode | null, currentView: LViewData): boolean {
if (child !== null && canInsertNativeNode(parent, currentView)) {
// We only add the element if not in View or not projected.
const renderer = currentView[RENDERER];
if (parent.tNode.type === TNodeType.View) {
const container = getParentLNode(parent) as LContainerNode;
const renderParent = container.data[RENDER_PARENT];
const views = container.data[VIEWS];
const index = views.indexOf(parent as LViewNode);
const beforeNode =
index + 1 < views.length ? (getChildLNode(views[index + 1]) !).native : container.native;
isProceduralRenderer(renderer) ?
renderer.insertBefore(renderParent !.native, child, beforeNode) :
renderParent !.native.insertBefore(child, beforeNode, true);
} else {
isProceduralRenderer(renderer) ? renderer.appendChild(parent.native !as RElement, child) :
parent.native !.appendChild(child);
}
return true;
}
return false;

View File

@ -57,10 +57,10 @@
"name": "RENDERER"
},
{
"name": "ROOT_DIRECTIVE_INDICES"
"name": "RENDER_PARENT"
},
{
"name": "SANITIZER"
"name": "ROOT_DIRECTIVE_INDICES"
},
{
"name": "TVIEW"
@ -156,7 +156,7 @@
"name": "firstTemplatePass"
},
{
"name": "getCurrentSanitizer"
"name": "getChildLNode"
},
{
"name": "getDirectiveInstance"

View File

@ -83,6 +83,9 @@
{
"name": "RENDERER"
},
{
"name": "RENDER_PARENT"
},
{
"name": "ROOT_DIRECTIVE_INDICES"
},
@ -335,6 +338,9 @@
{
"name": "generatePropertyAliases"
},
{
"name": "getChildLNode"
},
{
"name": "getCleanup"
},

View File

@ -261,6 +261,11 @@ describe('ViewContainerRef', () => {
insertTpl(ctx: {}) { this._vcRef.createEmbeddedView(this._tplRef, ctx); }
insertTpl2(ctx: {}) {
const viewRef = this._tplRef.createEmbeddedView(ctx);
this._vcRef.insert(viewRef);
}
remove(index?: number) { this._vcRef.remove(index); }
}
@ -327,7 +332,7 @@ describe('ViewContainerRef', () => {
fixture.update();
expect(fixture.html).toEqual('before|A|after');
directiveInstance !.insertTpl({});
directiveInstance !.insertTpl2({});
expect(fixture.html).toEqual('before|AA|after');
fixture.component.condition = true;