diff --git a/packages/core/src/render3/component.ts b/packages/core/src/render3/component.ts index 1c4d9f2319..2d4f94ae0f 100644 --- a/packages/core/src/render3/component.ts +++ b/packages/core/src/render3/component.ts @@ -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( 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( 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(); } diff --git a/packages/core/src/render3/component_ref.ts b/packages/core/src/render3/component_ref.ts index 20be059c13..4e64d9a310 100644 --- a/packages/core/src/render3/component_ref.ts +++ b/packages/core/src/render3/component_ref.ts @@ -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 extends viewEngine_ComponentFactory { } // 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 extends viewEngine_ComponentFactory { 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 extends viewEngine_ComponentFactory { 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( diff --git a/packages/core/src/render3/i18n.ts b/packages/core/src/render3/i18n.ts index 97bb07b7fb..397986b14f 100644 --- a/packages/core/src/render3/i18n.ts +++ b/packages/core/src/render3/i18n.ts @@ -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; } diff --git a/packages/core/src/render3/instructions/embedded_view.ts b/packages/core/src/render3/instructions/embedded_view.ts index 19b06480df..1e65eebd88 100644 --- a/packages/core/src/render3/instructions/embedded_view.ts +++ b/packages/core/src/render3/instructions/embedded_view.ts @@ -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); } diff --git a/packages/core/src/render3/instructions/shared.ts b/packages/core/src/render3/instructions/shared.ts index 512bb313a0..c532d8a128 100644 --- a/packages/core/src/render3/instructions/shared.ts +++ b/packages/core/src/render3/instructions/shared.ts @@ -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( } /** - * 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(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(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( + 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( - hostView: LView, context: T, templateFn?: ComponentTemplate) { + 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( } } -/** - * 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(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(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(flags: RenderFlags, tView: TView, component: T): void { - const viewQuery = tView.viewQuery; - if (viewQuery !== null) { - setCurrentQueryIndex(0); - viewQuery(flags, component); - } +function executeViewQueryFn( + flags: RenderFlags, viewQueryFn: ViewQueriesFunction<{}>, component: T): void { + ngDevMode && assertDefined(viewQueryFn, 'View queries function to execute must be defined.'); + setCurrentQueryIndex(0); + viewQueryFn(flags, component); } diff --git a/packages/core/src/render3/state.ts b/packages/core/src/render3/state.ts index 0add34b244..22591942e3 100644 --- a/packages/core/src/render3/state.ts +++ b/packages/core/src/render3/state.ts @@ -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); } diff --git a/packages/core/src/render3/util/view_utils.ts b/packages/core/src/render3/util/view_utils.ts index 11965632d6..6df568037f 100644 --- a/packages/core/src/render3/util/view_utils.ts +++ b/packages/core/src/render3/util/view_utils.ts @@ -135,6 +135,7 @@ export function load(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; diff --git a/packages/core/src/render3/view_engine_compatibility.ts b/packages/core/src/render3/view_engine_compatibility.ts index 4f6aca6783..a83f6673aa 100644 --- a/packages/core/src/render3/view_engine_compatibility.ts +++ b/packages/core/src/render3/view_engine_compatibility.ts @@ -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( 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; diff --git a/packages/core/test/acceptance/view_container_ref_spec.ts b/packages/core/test/acceptance/view_container_ref_spec.ts index 3eed859422..0d19108150 100644 --- a/packages/core/test/acceptance/view_container_ref_spec.ts +++ b/packages/core/test/acceptance/view_container_ref_spec.ts @@ -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: + `
before|middle|after
` + }) + 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 { diff --git a/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json b/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json index b9cf299485..83513bac59 100644 --- a/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json +++ b/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json @@ -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" }, diff --git a/packages/core/test/bundling/hello_world/bundle.golden_symbols.json b/packages/core/test/bundling/hello_world/bundle.golden_symbols.json index fb93df66e9..8846c561f3 100644 --- a/packages/core/test/bundling/hello_world/bundle.golden_symbols.json +++ b/packages/core/test/bundling/hello_world/bundle.golden_symbols.json @@ -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" }, diff --git a/packages/core/test/bundling/todo/bundle.golden_symbols.json b/packages/core/test/bundling/todo/bundle.golden_symbols.json index db738999de..fac9ea9d8b 100644 --- a/packages/core/test/bundling/todo/bundle.golden_symbols.json +++ b/packages/core/test/bundling/todo/bundle.golden_symbols.json @@ -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" }, diff --git a/packages/core/test/render3/di_spec.ts b/packages/core/test/render3/di_spec.ts index d9c44bb4e3..4bc84f84f5 100644 --- a/packages/core/test/render3/di_spec.ts +++ b/packages/core/test/render3/di_spec.ts @@ -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); } }); }); diff --git a/packages/core/test/render3/render_util.ts b/packages/core/test/render3/render_util.ts index ef7d51ae53..9e0a492bf4 100644 --- a/packages/core/test/render3/render_util.ts +++ b/packages/core/test/render3/render_util.ts @@ -277,7 +277,7 @@ export function renderTemplate( hostLView, componentTView, context, LViewFlags.CheckAlways, hostNode, hostTNode, providedRendererFactory, renderer, sanitizer); } - renderComponentOrTemplate(componentView, context, templateFn); + renderComponentOrTemplate(componentView, templateFn, context); return componentView; }