perf(ivy): split view processing into render (create) and refresh (update) pass (#32020)

PR Close #32020
This commit is contained in:
Pawel Kozlowski 2019-08-02 16:43:10 +02:00 committed by Kara Erickson
parent 4d96cf5197
commit b9dfe66028
14 changed files with 247 additions and 274 deletions

View File

@ -17,17 +17,17 @@ import {assertComponentType} from './assert';
import {getComponentDef} from './definition';
import {diPublicInInjector, getOrCreateNodeInjectorForNode} from './di';
import {registerPostOrderHooks, registerPreOrderHooks} from './hooks';
import {CLEAN_PROMISE, addToViewTree, createLView, createTView, getOrCreateTNode, getOrCreateTView, initNodeFlags, instantiateRootComponent, invokeHostBindingsInCreationMode, locateHostElement, markAsComponentHost, refreshDescendantViews} from './instructions/shared';
import {CLEAN_PROMISE, addToViewTree, createLView, createTView, getOrCreateTNode, getOrCreateTView, initNodeFlags, instantiateRootComponent, invokeHostBindingsInCreationMode, locateHostElement, markAsComponentHost, refreshView, renderView} from './instructions/shared';
import {ComponentDef, ComponentType, RenderFlags} from './interfaces/definition';
import {TElementNode, TNode, TNodeType} from './interfaces/node';
import {PlayerHandler} from './interfaces/player';
import {RElement, Renderer3, RendererFactory3, domRendererFactory3} from './interfaces/renderer';
import {CONTEXT, FLAGS, HEADER_OFFSET, LView, LViewFlags, RootContext, RootContextFlags, TVIEW} from './interfaces/view';
import {CONTEXT, HEADER_OFFSET, LView, LViewFlags, RootContext, RootContextFlags, TVIEW} from './interfaces/view';
import {enterView, getPreviousOrParentTNode, leaveView, resetComponentState, setActiveHostElement} from './state';
import {publishDefaultGlobalUtils} from './util/global_utils';
import {defaultScheduler, stringifyForError} from './util/misc_utils';
import {getRootContext} from './util/view_traversal_utils';
import {readPatchedLView, resetPreOrderHookFlags} from './util/view_utils';
import {readPatchedLView} from './util/view_utils';
@ -128,15 +128,14 @@ export function renderComponent<T>(
const rootContext = createRootContext(opts.scheduler, opts.playerHandler);
const renderer = rendererFactory.createRenderer(hostRNode, componentDef);
const rootTView = createTView(-1, null, 1, 0, null, null, null, null);
const rootView: LView = createLView(
null, createTView(-1, null, 1, 0, null, null, null, null), rootContext, rootFlags, null, null,
rendererFactory, renderer, undefined, opts.injector || null);
null, rootTView, rootContext, rootFlags, null, null, rendererFactory, renderer, undefined,
opts.injector || null);
const oldView = enterView(rootView, null);
let component: T;
// Will become true if the `try` block executes with no errors.
let safeToRunHooks = false;
try {
if (rendererFactory.begin) rendererFactory.begin();
const componentView = createRootComponentView(
@ -144,13 +143,13 @@ export function renderComponent<T>(
component = createRootComponent(
componentView, componentDef, rootView, rootContext, opts.hostFeatures || null);
refreshDescendantViews(rootView); // creation mode pass
rootView[FLAGS] &= ~LViewFlags.CreationMode;
resetPreOrderHookFlags(rootView);
refreshDescendantViews(rootView); // update mode pass
safeToRunHooks = true;
// create mode pass
renderView(rootView, rootTView, null);
// update mode pass
refreshView(rootView, rootTView, null, null);
} finally {
leaveView(oldView, safeToRunHooks);
leaveView(oldView);
if (rendererFactory.end) rendererFactory.end();
}

View File

@ -24,7 +24,7 @@ import {assertComponentType} from './assert';
import {LifecycleHooksFeature, createRootComponent, createRootComponentView, createRootContext} from './component';
import {getComponentDef} from './definition';
import {NodeInjector} from './di';
import {addToViewTree, assignTViewNodeToLView, createLView, createTView, elementCreate, locateHostElement, refreshDescendantViews} from './instructions/shared';
import {assignTViewNodeToLView, createLView, createTView, elementCreate, locateHostElement, renderView} from './instructions/shared';
import {ComponentDef} from './interfaces/definition';
import {TContainerNode, TElementContainerNode, TElementNode} from './interfaces/node';
import {RNode, RendererFactory3, domRendererFactory3, isProceduralRenderer} from './interfaces/renderer';
@ -161,9 +161,10 @@ export class ComponentFactory<T> extends viewEngine_ComponentFactory<T> {
}
// Create the root view. Uses empty TView and ContentTemplate.
const rootTView = createTView(-1, null, 1, 0, null, null, null, null);
const rootLView = createLView(
null, createTView(-1, null, 1, 0, null, null, null, null), rootContext, rootFlags, null,
null, rendererFactory, renderer, sanitizer, rootViewInjector);
null, rootTView, rootContext, rootFlags, null, null, rendererFactory, renderer, sanitizer,
rootViewInjector);
// rootView is the parent when bootstrapping
const oldLView = enterView(rootLView, null);
@ -171,8 +172,6 @@ export class ComponentFactory<T> extends viewEngine_ComponentFactory<T> {
let component: T;
let tElementNode: TElementNode;
// Will become true if the `try` block executes with no errors.
let safeToRunHooks = false;
try {
const componentView = createRootComponentView(
hostRNode, this.componentDef, rootLView, rendererFactory, renderer);
@ -193,10 +192,9 @@ export class ComponentFactory<T> extends viewEngine_ComponentFactory<T> {
component = createRootComponent(
componentView, this.componentDef, rootLView, rootContext, [LifecycleHooksFeature]);
refreshDescendantViews(rootLView);
safeToRunHooks = true;
renderView(rootLView, rootTView, null);
} finally {
leaveView(oldLView, safeToRunHooks);
leaveView(oldLView);
}
const componentRef = new ComponentRef(

View File

@ -705,7 +705,7 @@ function createDynamicNodeAtIndex(
// We are creating a dynamic node, the previous tNode might not be pointing at this node.
// We will link ourselves into the tree later with `appendI18nNode`.
if (previousOrParentTNode.next === tNode) {
if (previousOrParentTNode && previousOrParentTNode.next === tNode) {
previousOrParentTNode.next = null;
}

View File

@ -11,12 +11,14 @@ import {assertLContainerOrUndefined} from '../assert';
import {ACTIVE_INDEX, CONTAINER_HEADER_OFFSET, LContainer} from '../interfaces/container';
import {RenderFlags} from '../interfaces/definition';
import {TContainerNode, TNodeType} from '../interfaces/node';
import {FLAGS, LView, LViewFlags, PARENT, TVIEW, TView, T_HOST} from '../interfaces/view';
import {CONTEXT, LView, LViewFlags, PARENT, TVIEW, TView, T_HOST} from '../interfaces/view';
import {assertNodeType} from '../node_assert';
import {insertView, removeView} from '../node_manipulation';
import {enterView, getIsParent, getLView, getPreviousOrParentTNode, leaveView, setIsParent, setPreviousOrParentTNode} from '../state';
import {isCreationMode, resetPreOrderHookFlags} from '../util/view_utils';
import {assignTViewNodeToLView, createLView, createTView, refreshDescendantViews} from './shared';
import {isCreationMode} from '../util/view_utils';
import {assignTViewNodeToLView, createLView, createTView, refreshView, renderView} from './shared';
/**
* Marks the start of an embedded view.
@ -126,19 +128,17 @@ function scanForView(lContainer: LContainer, startIdx: number, viewBlockId: numb
*/
export function ɵɵembeddedViewEnd(): void {
const lView = getLView();
const tView = lView[TVIEW];
const viewHost = lView[T_HOST];
const context = lView[CONTEXT];
if (isCreationMode(lView)) {
refreshDescendantViews(lView); // creation mode pass
lView[FLAGS] &= ~LViewFlags.CreationMode;
renderView(lView, tView, context); // creation mode pass
}
resetPreOrderHookFlags(lView);
refreshDescendantViews(lView); // update mode pass
refreshView(lView, tView, tView.template, context); // update mode pass
const lContainer = lView[PARENT] as LContainer;
ngDevMode && assertLContainerOrUndefined(lContainer);
// It's always safe to run hooks here, as `leaveView` is not called during the 'finally' block
// of a try-catch-finally statement, so it can never be reached while unwinding the stack due to
// an error being thrown.
leaveView(lContainer[PARENT] !, /* safeToRunHooks */ true);
leaveView(lContainer[PARENT] !);
setPreviousOrParentTNode(viewHost !, false);
}

View File

@ -26,7 +26,7 @@ import {RComment, RElement, RText, Renderer3, RendererFactory3, isProceduralRend
import {SanitizerFn} from '../interfaces/sanitization';
import {isComponent, isComponentDef, isContentQueryHost, isLContainer, isRootView} from '../interfaces/type_checks';
import {BINDING_INDEX, CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTEXT, DECLARATION_VIEW, ExpandoInstructions, FLAGS, HEADER_OFFSET, HOST, INJECTOR, InitPhaseState, LView, LViewFlags, NEXT, PARENT, RENDERER, RENDERER_FACTORY, RootContext, RootContextFlags, SANITIZER, TData, TVIEW, TView, T_HOST} from '../interfaces/view';
import {assertNodeOfPossibleTypes, assertNodeType} from '../node_assert';
import {assertNodeOfPossibleTypes} from '../node_assert';
import {isNodeMatchingSelectorList} from '../node_selector_matcher';
import {enterView, getBindingsEnabled, getCheckNoChangesMode, getIsParent, getLView, getPreviousOrParentTNode, getSelectedIndex, incrementActiveDirectiveId, leaveView, namespaceHTMLInternal, setActiveHostElement, setBindingRoot, setCheckNoChangesMode, setCurrentDirectiveDef, setCurrentQueryIndex, setPreviousOrParentTNode, setSelectedIndex} from '../state';
import {renderStylingMap} from '../styling_next/bindings';
@ -52,69 +52,6 @@ export const enum BindingDirection {
Output,
}
/**
* Refreshes the view, executing the following steps in that order:
* triggers init hooks, refreshes dynamic embedded views, triggers content hooks, sets host
* bindings, refreshes child components.
* Note: view hooks are triggered later when leaving the view.
*/
export function refreshDescendantViews(lView: LView) {
const tView = lView[TVIEW];
const creationMode = isCreationMode(lView);
if (!creationMode) {
// Resetting the bindingIndex of the current LView as the next steps may trigger change
// detection.
lView[BINDING_INDEX] = tView.bindingStartIndex;
const checkNoChangesMode = getCheckNoChangesMode();
executePreOrderHooks(lView, tView, checkNoChangesMode, undefined);
refreshDynamicEmbeddedViews(lView);
// Content query results must be refreshed before content hooks are called.
if (tView.contentQueries !== null) {
refreshContentQueries(tView, lView);
}
resetPreOrderHookFlags(lView);
executeHooks(
lView, tView.contentHooks, tView.contentCheckHooks, checkNoChangesMode,
InitPhaseState.AfterContentInitHooksToBeRun, undefined);
setHostBindings(tView, lView);
} else {
// This needs to be set before children are processed to support recursive components.
// This must be set to false immediately after the first creation run because in an
// ngFor loop, all the views will be created together before update mode runs and turns
// off firstTemplatePass. If we don't set it here, instances will perform directive
// matching, etc again and again.
tView.firstTemplatePass = false;
// We resolve content queries specifically marked as `static` in creation mode. Dynamic
// content queries are resolved during change detection (i.e. update mode), after embedded
// views are refreshed (see block above).
if (tView.staticContentQueries) {
refreshContentQueries(tView, lView);
}
}
// We must materialize query results before child components are processed
// in case a child component has projected a container. The LContainer needs
// to exist so the embedded views are properly attached by the container.
if (!creationMode || tView.staticViewQueries) {
executeViewQueryFn(RenderFlags.Update, tView, lView[CONTEXT]);
}
const components = tView.components;
if (components !== null) {
refreshChildComponents(lView, components);
}
}
/** Sets the host bindings for the current view. */
export function setHostBindings(tView: TView, viewData: LView): void {
const selectedIndex = getSelectedIndex();
@ -186,13 +123,19 @@ function refreshContentQueries(tView: TView, lView: LView): void {
}
}
/** Refreshes child components in the current view. */
/** Refreshes child components in the current view (update mode). */
function refreshChildComponents(hostLView: LView, components: number[]): void {
for (let i = 0; i < components.length; i++) {
componentRefresh(hostLView, components[i]);
refreshComponent(hostLView, components[i]);
}
}
/** Renders child components in the current view (creation mode). */
function renderChildComponents(hostLView: LView, components: number[]): void {
for (let i = 0; i < components.length; i++) {
renderComponent(hostLView, components[i]);
}
}
/**
* Creates a native element from a tag name, using a renderer.
@ -380,70 +323,145 @@ export function createEmbeddedViewAndNode<T>(
}
/**
* Used for rendering views in a LContainer (embedded views or root component views for dynamically
* created components).
*
* Dynamically created views must store/retrieve their TViews differently from component views
* because their template functions are nested in the template functions of their hosts, creating
* closures. If their host template happens to be an embedded template in a loop (e.g. ngFor
* inside
* an ngFor), the nesting would mean we'd have multiple instances of the template function, so we
* can't store TViews in the template function itself (as we do for comps). Instead, we store the
* TView for dynamically created views on their host TNode, which only has one instance.
* Processes a view in the creation mode. This includes a number of steps in a specific order:
* - creating view query functions (if any);
* - executing a template function in the creation mode;
* - updating static queries (if any);
* - creating child components defined in a given view.
*/
export function renderEmbeddedTemplate<T>(viewToRender: LView, tView: TView, context: T) {
const _isParent = getIsParent();
const _previousOrParentTNode = getPreviousOrParentTNode();
let oldView: LView;
// Will become true if the `try` block executes with no errors.
let safeToRunHooks = false;
export function renderView<T>(lView: LView, tView: TView, context: T): void {
ngDevMode && assertEqual(isCreationMode(lView), true, 'Should be run in creation mode');
const oldView = enterView(lView, lView[T_HOST]);
try {
oldView = enterView(viewToRender, viewToRender[T_HOST]);
resetPreOrderHookFlags(viewToRender);
const viewQuery = tView.viewQuery;
if (viewQuery !== null) {
executeViewQueryFn(RenderFlags.Create, viewQuery, context);
}
// Execute a template associated with this view, if it exists. A template function might not be
// defined for the root component views.
const templateFn = tView.template;
if (templateFn !== null) {
executeTemplate(viewToRender, templateFn, getRenderFlags(viewToRender), context);
executeTemplate(lView, templateFn, RenderFlags.Create, context);
}
refreshDescendantViews(viewToRender);
safeToRunHooks = true;
// This needs to be set before children are processed to support recursive components.
// This must be set to false immediately after the first creation run because in an
// ngFor loop, all the views will be created together before update mode runs and turns
// off firstTemplatePass. If we don't set it here, instances will perform directive
// matching, etc again and again.
if (tView.firstTemplatePass) {
tView.firstTemplatePass = false;
}
// We resolve content queries specifically marked as `static` in creation mode. Dynamic
// content queries are resolved during change detection (i.e. update mode), after embedded
// views are refreshed (see block above).
if (tView.staticContentQueries) {
refreshContentQueries(tView, lView);
}
// We must materialize query results before child components are processed
// in case a child component has projected a container. The LContainer needs
// to exist so the embedded views are properly attached by the container.
if (tView.staticViewQueries) {
executeViewQueryFn(RenderFlags.Update, tView.viewQuery !, context);
}
// Render child component views.
const components = tView.components;
if (components !== null) {
renderChildComponents(lView, components);
}
} finally {
leaveView(oldView !, safeToRunHooks);
setPreviousOrParentTNode(_previousOrParentTNode, _isParent);
lView[FLAGS] &= ~LViewFlags.CreationMode;
leaveView(oldView);
}
}
/**
* Processes a view in update mode. This includes a number of steps in a specific order:
* - executing a template function in update mode;
* - executing hooks;
* - refreshing queries;
* - setting host bindings;
* - refreshing child (embedded and component) views.
*/
export function refreshView<T>(
lView: LView, tView: TView, templateFn: ComponentTemplate<{}>| null, context: T) {
ngDevMode && assertEqual(isCreationMode(lView), false, 'Should be run in update mode');
let oldView = enterView(lView, lView[T_HOST]);
try {
resetPreOrderHookFlags(lView);
if (templateFn !== null) {
executeTemplate(lView, templateFn, RenderFlags.Update, context);
}
// Resetting the bindingIndex of the current LView as the next steps may trigger change
// detection.
lView[BINDING_INDEX] = tView.bindingStartIndex;
const checkNoChangesMode = getCheckNoChangesMode();
executePreOrderHooks(lView, tView, checkNoChangesMode, undefined);
refreshDynamicEmbeddedViews(lView);
// Content query results must be refreshed before content hooks are called.
if (tView.contentQueries !== null) {
refreshContentQueries(tView, lView);
}
resetPreOrderHookFlags(lView);
executeHooks(
lView, tView.contentHooks, tView.contentCheckHooks, checkNoChangesMode,
InitPhaseState.AfterContentInitHooksToBeRun, undefined);
setHostBindings(tView, lView);
const viewQuery = tView.viewQuery;
if (viewQuery !== null) {
executeViewQueryFn(RenderFlags.Update, viewQuery, context);
}
// Refresh child component views.
const components = tView.components;
if (components !== null) {
refreshChildComponents(lView, components);
}
resetPreOrderHookFlags(lView);
executeHooks(
lView, tView.viewHooks, tView.viewCheckHooks, checkNoChangesMode,
InitPhaseState.AfterViewInitHooksToBeRun, undefined);
} finally {
lView[FLAGS] &= ~(LViewFlags.Dirty | LViewFlags.FirstLViewPass);
lView[BINDING_INDEX] = tView.bindingStartIndex;
leaveView(oldView);
}
}
export function renderComponentOrTemplate<T>(
hostView: LView, context: T, templateFn?: ComponentTemplate<T>) {
hostView: LView, templateFn: ComponentTemplate<{}>| null, context: T) {
const rendererFactory = hostView[RENDERER_FACTORY];
const oldView = enterView(hostView, hostView[T_HOST]);
const normalExecutionPath = !getCheckNoChangesMode();
const creationModeIsActive = isCreationMode(hostView);
// Will become true if the `try` block executes with no errors.
let safeToRunHooks = false;
try {
if (normalExecutionPath && !creationModeIsActive && rendererFactory.begin) {
rendererFactory.begin();
}
const tView = hostView[TVIEW];
if (creationModeIsActive) {
// creation mode pass
templateFn && executeTemplate(hostView, templateFn, RenderFlags.Create, context);
refreshDescendantViews(hostView);
hostView[FLAGS] &= ~LViewFlags.CreationMode;
renderView(hostView, tView, context);
}
// update mode pass
resetPreOrderHookFlags(hostView);
templateFn && executeTemplate(hostView, templateFn, RenderFlags.Update, context);
refreshDescendantViews(hostView);
safeToRunHooks = true;
refreshView(hostView, tView, templateFn, context);
} finally {
if (normalExecutionPath && !creationModeIsActive && rendererFactory.end) {
rendererFactory.end();
}
leaveView(oldView, safeToRunHooks);
}
}
@ -464,15 +482,6 @@ function executeTemplate<T>(
}
}
/**
* This function returns the default configuration of rendering flags depending on when the
* template is in creation mode or update mode. Update block and create block are
* always run separately.
*/
function getRenderFlags(view: LView): RenderFlags {
return isCreationMode(view) ? RenderFlags.Create : RenderFlags.Update;
}
//////////////////////////
//// Element
//////////////////////////
@ -1469,8 +1478,7 @@ export function createLContainer(
/**
* Goes over dynamic embedded views (ones created through ViewContainerRef APIs) and refreshes
* them
* by executing an associated template function.
* them by executing an associated template function.
*/
function refreshDynamicEmbeddedViews(lView: LView) {
let viewOrContainer = lView[CHILD_HEAD];
@ -1480,10 +1488,9 @@ function refreshDynamicEmbeddedViews(lView: LView) {
if (isLContainer(viewOrContainer) && viewOrContainer[ACTIVE_INDEX] === -1) {
for (let i = CONTAINER_HEADER_OFFSET; i < viewOrContainer.length; i++) {
const embeddedLView = viewOrContainer[i];
// The directives and pipes are not needed here as an existing view is only being
// refreshed.
ngDevMode && assertDefined(embeddedLView[TVIEW], 'TView must be allocated');
renderEmbeddedTemplate(embeddedLView, embeddedLView[TVIEW], embeddedLView[CONTEXT] !);
const embeddedTView = embeddedLView[TVIEW];
ngDevMode && assertDefined(embeddedTView, 'TView must be allocated');
refreshView(embeddedLView, embeddedTView, embeddedTView.template, embeddedLView[CONTEXT] !);
}
}
viewOrContainer = viewOrContainer[NEXT];
@ -1497,23 +1504,26 @@ function refreshDynamicEmbeddedViews(lView: LView) {
/**
* Refreshes components by entering the component view and processing its bindings, queries, etc.
*
* @param adjustedElementIndex Element index in LView[] (adjusted for HEADER_OFFSET)
* @param componentHostIdx Element index in LView[] (adjusted for HEADER_OFFSET)
*/
export function componentRefresh(hostLView: LView, adjustedElementIndex: number): void {
ngDevMode && assertDataInRange(hostLView, adjustedElementIndex);
const componentView = getComponentViewByIndex(adjustedElementIndex, hostLView);
ngDevMode &&
assertNodeType(hostLView[TVIEW].data[adjustedElementIndex] as TNode, TNodeType.Element);
// Only components in creation mode, attached CheckAlways
// components or attached, dirty OnPush components should be checked
if ((viewAttachedToChangeDetector(componentView) || isCreationMode(hostLView)) &&
function refreshComponent(hostLView: LView, componentHostIdx: number): void {
ngDevMode && assertEqual(isCreationMode(hostLView), false, 'Should be run in update mode');
const componentView = getComponentViewByIndex(componentHostIdx, hostLView);
// Only attached components that are CheckAlways or OnPush and dirty should be refreshed
if (viewAttachedToChangeDetector(componentView) &&
componentView[FLAGS] & (LViewFlags.CheckAlways | LViewFlags.Dirty)) {
syncViewWithBlueprint(componentView);
checkView(componentView, componentView[CONTEXT]);
const tView = componentView[TVIEW];
refreshView(componentView, tView, tView.template, componentView[CONTEXT]);
}
}
function renderComponent(hostLView: LView, componentHostIdx: number) {
ngDevMode && assertEqual(isCreationMode(hostLView), true, 'Should be run in creation mode');
const componentView = getComponentViewByIndex(componentHostIdx, hostLView);
syncViewWithBlueprint(componentView);
renderView(componentView, componentView[TVIEW], componentView[CONTEXT]);
}
/**
* Syncs an LView instance with its blueprint if they have gotten out of sync.
*
@ -1647,7 +1657,9 @@ export function scheduleTick(rootContext: RootContext, flags: RootContextFlags)
export function tickRootContext(rootContext: RootContext) {
for (let i = 0; i < rootContext.components.length; i++) {
const rootComponent = rootContext.components[i];
renderComponentOrTemplate(readPatchedLView(rootComponent) !, rootComponent);
const lView = readPatchedLView(rootComponent) !;
const tView = lView[TVIEW];
renderComponentOrTemplate(lView, tView.template, rootComponent);
}
}
@ -1655,12 +1667,9 @@ export function detectChangesInternal<T>(view: LView, context: T) {
const rendererFactory = view[RENDERER_FACTORY];
if (rendererFactory.begin) rendererFactory.begin();
try {
if (isCreationMode(view)) {
checkView(view, context); // creation mode pass
}
checkView(view, context); // update mode pass
const tView = view[TVIEW];
refreshView(view, tView, tView.template, context);
} catch (error) {
handleError(view, error);
throw error;
@ -1717,33 +1726,11 @@ export function checkNoChangesInRootView(lView: LView): void {
}
}
/** Checks the view of the component provided. Does not gate on dirty checks or execute doCheck.
*/
export function checkView<T>(hostView: LView, component: T) {
const hostTView = hostView[TVIEW];
const oldView = enterView(hostView, hostView[T_HOST]);
const templateFn = hostTView.template !;
const creationMode = isCreationMode(hostView);
// Will become true if the `try` block executes with no errors.
let safeToRunHooks = false;
try {
resetPreOrderHookFlags(hostView);
creationMode && executeViewQueryFn(RenderFlags.Create, hostTView, component);
executeTemplate(hostView, templateFn, getRenderFlags(hostView), component);
refreshDescendantViews(hostView);
safeToRunHooks = true;
} finally {
leaveView(oldView, safeToRunHooks);
}
}
function executeViewQueryFn<T>(flags: RenderFlags, tView: TView, component: T): void {
const viewQuery = tView.viewQuery;
if (viewQuery !== null) {
setCurrentQueryIndex(0);
viewQuery(flags, component);
}
function executeViewQueryFn<T>(
flags: RenderFlags, viewQueryFn: ViewQueriesFunction<{}>, component: T): void {
ngDevMode && assertDefined(viewQueryFn, 'View queries function to execute must be defined.');
setCurrentQueryIndex(0);
viewQueryFn(flags, component);
}

View File

@ -10,13 +10,10 @@ import {StyleSanitizeFn} from '../sanitization/style_sanitizer';
import {assertDefined} from '../util/assert';
import {assertLViewOrUndefined} from './assert';
import {executeHooks} from './hooks';
import {ComponentDef, DirectiveDef} from './interfaces/definition';
import {TElementNode, TNode, TViewNode} from './interfaces/node';
import {BINDING_INDEX, CONTEXT, DECLARATION_VIEW, FLAGS, InitPhaseState, LView, LViewFlags, OpaqueViewState, TVIEW} from './interfaces/view';
import {CONTEXT, DECLARATION_VIEW, LView, OpaqueViewState, TVIEW} from './interfaces/view';
import {resetAllStylingState, resetStylingState} from './styling_next/state';
import {isCreationMode, resetPreOrderHookFlags} from './util/view_utils';
/**
@ -461,27 +458,9 @@ export function resetComponentState() {
* Used in lieu of enterView to make it clear when we are exiting a child view. This makes
* the direction of traversal (up or down the view tree) a bit clearer.
*
* @param newView New state to become active
* @param safeToRunHooks Whether the runtime is in a state where running lifecycle hooks is valid.
* This is not always the case (for example, the application may have crashed and `leaveView` is
* being executed while unwinding the call stack).
* @param newView New LView to become active
*/
export function leaveView(newView: LView, safeToRunHooks: boolean): void {
const tView = lView[TVIEW];
if (isCreationMode(lView)) {
lView[FLAGS] &= ~LViewFlags.CreationMode;
} else {
try {
resetPreOrderHookFlags(lView);
safeToRunHooks && executeHooks(
lView, tView.viewHooks, tView.viewCheckHooks, checkNoChangesMode,
InitPhaseState.AfterViewInitHooksToBeRun, undefined);
} finally {
// Views are clean and in update mode after being checked, so these bits are cleared
lView[FLAGS] &= ~(LViewFlags.Dirty | LViewFlags.FirstLViewPass);
lView[BINDING_INDEX] = tView.bindingStartIndex;
}
}
export function leaveView(newView: LView): void {
enterView(newView, null);
}

View File

@ -135,6 +135,7 @@ export function load<T>(view: LView | TData, index: number): T {
export function getComponentViewByIndex(nodeIndex: number, hostView: LView): LView {
// Could be an LView or an LContainer. If LContainer, unwrap to find LView.
ngDevMode && assertDataInRange(hostView, nodeIndex);
const slotValue = hostView[nodeIndex];
const lView = isLView(slotValue) ? slotValue : slotValue[HOST];
return lView;

View File

@ -19,7 +19,7 @@ import {addToArray, removeFromArray} from '../util/array_utils';
import {assertDefined, assertGreaterThan, assertLessThan} from '../util/assert';
import {assertLContainer} from './assert';
import {NodeInjector, getParentInjectorLocation} from './di';
import {addToViewTree, createEmbeddedViewAndNode, createLContainer, renderEmbeddedTemplate} from './instructions/shared';
import {addToViewTree, createEmbeddedViewAndNode, createLContainer, renderView} from './instructions/shared';
import {ACTIVE_INDEX, CONTAINER_HEADER_OFFSET, LContainer, VIEW_REFS} from './interfaces/container';
import {TContainerNode, TElementContainerNode, TElementNode, TNode, TNodeType, TViewNode} from './interfaces/node';
import {RComment, RElement, isProceduralRenderer} from './interfaces/renderer';
@ -121,7 +121,8 @@ export function createTemplateRef<T>(
lView[QUERIES] = declarationViewLQueries.createEmbeddedView(embeddedTView);
}
renderEmbeddedTemplate(lView, embeddedTView, context);
renderView(lView, embeddedTView, context);
const viewRef = new ViewRef(lView, context, -1);
viewRef._tViewNode = lView[T_HOST] as TViewNode;
return viewRef;

View File

@ -61,6 +61,22 @@ describe('ViewContainerRef', () => {
expect(fixture.componentInstance.foo).toBeAnInstanceOf(TemplateRef);
});
it('should construct proper TNode / DOM tree when embedded views are created in a directive constructor',
() => {
@Component({
selector: 'view-insertion-test-cmpt',
template:
`<div>before<ng-template constructorDir><span>|middle|</span></ng-template>after</div>`
})
class ViewInsertionTestCmpt {
}
TestBed.configureTestingModule({declarations: [ViewInsertionTestCmpt, ConstructorDir]});
const fixture = TestBed.createComponent(ViewInsertionTestCmpt);
expect(fixture.nativeElement).toHaveText('before|middle|after');
});
it('should use comment node of host ng-container as insertion marker', () => {
@Component({template: 'hello'})
class HelloComp {

View File

@ -203,12 +203,6 @@
{
"name": "checkNoChangesMode"
},
{
"name": "checkView"
},
{
"name": "componentRefresh"
},
{
"name": "concatString"
},
@ -398,9 +392,6 @@
{
"name": "getPreviousOrParentTNode"
},
{
"name": "getRenderFlags"
},
{
"name": "getRenderParent"
},
@ -464,9 +455,6 @@
{
"name": "isContentQueryHost"
},
{
"name": "isCreationMode"
},
{
"name": "isCssClassMatching"
},
@ -545,14 +533,17 @@
{
"name": "refreshChildComponents"
},
{
"name": "refreshComponent"
},
{
"name": "refreshContentQueries"
},
{
"name": "refreshDescendantViews"
"name": "refreshDynamicEmbeddedViews"
},
{
"name": "refreshDynamicEmbeddedViews"
"name": "refreshView"
},
{
"name": "registerInitialStylingOnTNode"
@ -563,11 +554,14 @@
{
"name": "registerPreOrderHooks"
},
{
"name": "renderChildComponents"
},
{
"name": "renderComponent"
},
{
"name": "renderEmbeddedTemplate"
"name": "renderComponent"
},
{
"name": "renderInitialStyling"
@ -578,6 +572,9 @@
{
"name": "renderStylingMap"
},
{
"name": "renderView"
},
{
"name": "resetAllStylingState"
},

View File

@ -173,12 +173,6 @@
{
"name": "checkNoChangesMode"
},
{
"name": "checkView"
},
{
"name": "componentRefresh"
},
{
"name": "createLView"
},
@ -320,9 +314,6 @@
{
"name": "getPreviousOrParentTNode"
},
{
"name": "getRenderFlags"
},
{
"name": "getRenderParent"
},
@ -356,9 +347,6 @@
{
"name": "isComponentDef"
},
{
"name": "isCreationMode"
},
{
"name": "isFactory"
},
@ -411,23 +399,32 @@
"name": "refreshChildComponents"
},
{
"name": "refreshContentQueries"
"name": "refreshComponent"
},
{
"name": "refreshDescendantViews"
"name": "refreshContentQueries"
},
{
"name": "refreshDynamicEmbeddedViews"
},
{
"name": "refreshView"
},
{
"name": "renderChildComponents"
},
{
"name": "renderComponent"
},
{
"name": "renderEmbeddedTemplate"
"name": "renderComponent"
},
{
"name": "renderStringify"
},
{
"name": "renderView"
},
{
"name": "resetAllStylingState"
},

View File

@ -509,18 +509,12 @@
{
"name": "checkNoChangesMode"
},
{
"name": "checkView"
},
{
"name": "cleanUpView"
},
{
"name": "collectNativeNodes"
},
{
"name": "componentRefresh"
},
{
"name": "concatString"
},
@ -890,9 +884,6 @@
{
"name": "getPropValuesStartPosition"
},
{
"name": "getRenderFlags"
},
{
"name": "getRenderParent"
},
@ -1184,14 +1175,17 @@
{
"name": "refreshChildComponents"
},
{
"name": "refreshComponent"
},
{
"name": "refreshContentQueries"
},
{
"name": "refreshDescendantViews"
"name": "refreshDynamicEmbeddedViews"
},
{
"name": "refreshDynamicEmbeddedViews"
"name": "refreshView"
},
{
"name": "registerBinding"
@ -1214,6 +1208,12 @@
{
"name": "removeView"
},
{
"name": "renderChildComponents"
},
{
"name": "renderComponent"
},
{
"name": "renderComponent"
},
@ -1223,9 +1223,6 @@
{
"name": "renderDetachView"
},
{
"name": "renderEmbeddedTemplate"
},
{
"name": "renderInitialStyling"
},
@ -1235,6 +1232,9 @@
{
"name": "renderStylingMap"
},
{
"name": "renderView"
},
{
"name": "resetAllStylingState"
},

View File

@ -607,7 +607,6 @@ describe('di', () => {
null, createTView(-1, null, 1, 0, null, null, null, null), null, LViewFlags.CheckAlways,
null, null, {} as any, {} as any);
const oldView = enterView(contentView, null);
let safeToRunHooks = false;
try {
const parentTNode =
getOrCreateTNode(contentView[TVIEW], null, 0, TNodeType.Element, null, null);
@ -618,9 +617,8 @@ describe('di', () => {
const injector = getOrCreateNodeInjectorForNode(parentTNode, contentView);
expect(injector).not.toEqual(-1);
safeToRunHooks = true;
} finally {
leaveView(oldView, safeToRunHooks);
leaveView(oldView);
}
});
});

View File

@ -277,7 +277,7 @@ export function renderTemplate<T>(
hostLView, componentTView, context, LViewFlags.CheckAlways, hostNode, hostTNode,
providedRendererFactory, renderer, sanitizer);
}
renderComponentOrTemplate(componentView, context, templateFn);
renderComponentOrTemplate(componentView, templateFn, context);
return componentView;
}