fix(core): Refresh transplanted views at insertion point only (#35968)
Only refresh transplanted views at the insertion location in Ivy. Previously, Ivy would check transplanted views at both the insertion and declaration points. This is achieved by adding a marker to the insertion tree when we encounter a transplanted view that needs to be refreshed at its declaration. We use this marker as an extra indication that we still need to descend and refresh those transplanted views at their insertion locations even if the insertion view and/or its parents are not dirty. This change fixes several issues: * Transplanted views refreshed twice if both insertion and declaration are dirty. This could be an error if the insertion component changes result in data not being available to the transplanted view because it is slated to be removed. * CheckAlways transplanted views not refreshed if shielded by non-dirty OnPush (fixes #35400) * Transplanted views still refreshed when insertion tree is detached (fixes #21324) PR Close #35968
This commit is contained in:

committed by
Andrew Kushnir

parent
e5f459d32b
commit
c8c2272a9f
@ -28,15 +28,15 @@ import {AttributeMarker, InitialInputData, InitialInputs, LocalRefExtractor, Pro
|
||||
import {isProceduralRenderer, RComment, RElement, Renderer3, RendererFactory3, RNode, RText} from '../interfaces/renderer';
|
||||
import {SanitizerFn} from '../interfaces/sanitization';
|
||||
import {isComponentDef, isComponentHost, isContentQueryHost, isLContainer, isRootView} from '../interfaces/type_checks';
|
||||
import {CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTEXT, DECLARATION_COMPONENT_VIEW, DECLARATION_VIEW, FLAGS, HEADER_OFFSET, HOST, InitPhaseState, INJECTOR, LView, LViewFlags, NEXT, PARENT, RENDERER, RENDERER_FACTORY, RootContext, RootContextFlags, SANITIZER, T_HOST, TData, TVIEW, TView, TViewType} from '../interfaces/view';
|
||||
import {CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTEXT, DECLARATION_COMPONENT_VIEW, DECLARATION_VIEW, FLAGS, HEADER_OFFSET, HOST, InitPhaseState, INJECTOR, LView, LViewFlags, NEXT, PARENT, RENDERER, RENDERER_FACTORY, RootContext, RootContextFlags, SANITIZER, T_HOST, TData, TRANSPLANTED_VIEWS_TO_REFRESH, TVIEW, TView, TViewType} from '../interfaces/view';
|
||||
import {assertNodeOfPossibleTypes} from '../node_assert';
|
||||
import {isInlineTemplate, isNodeMatchingSelectorList} from '../node_selector_matcher';
|
||||
import {enterView, getBindingsEnabled, getCheckNoChangesMode, getCurrentDirectiveIndex, getIsParent, getPreviousOrParentTNode, getSelectedIndex, getTView, leaveView, setBindingIndex, setBindingRootForHostBindings, setCheckNoChangesMode, setCurrentDirectiveIndex, setCurrentQueryIndex, setPreviousOrParentTNode, setSelectedIndex} from '../state';
|
||||
import {enterView, getBindingsEnabled, getCheckNoChangesMode, getCurrentDirectiveIndex, getIsParent, getPreviousOrParentTNode, getSelectedIndex, leaveView, setBindingIndex, setBindingRootForHostBindings, setCheckNoChangesMode, setCurrentDirectiveIndex, setCurrentQueryIndex, setPreviousOrParentTNode, setSelectedIndex} from '../state';
|
||||
import {NO_CHANGE} from '../tokens';
|
||||
import {isAnimationProp, mergeHostAttrs} from '../util/attrs_utils';
|
||||
import {INTERPOLATION_DELIMITER, renderStringify, stringifyForError} from '../util/misc_utils';
|
||||
import {getLViewParent} from '../util/view_traversal_utils';
|
||||
import {getComponentLViewByIndex, getNativeByIndex, getNativeByTNode, getTNode, isCreationMode, readPatchedLView, resetPreOrderHookFlags, unwrapLView, viewAttachedToChangeDetector} from '../util/view_utils';
|
||||
import {getComponentLViewByIndex, getNativeByIndex, getNativeByTNode, isCreationMode, readPatchedLView, resetPreOrderHookFlags, unwrapLView, updateTransplantedViewCount, viewAttachedToChangeDetector} from '../util/view_utils';
|
||||
|
||||
import {selectIndexInternal} from './advance';
|
||||
import {attachLContainerDebug, attachLViewDebug, cloneToLViewFromTViewBlueprint, cloneToTViewData, LCleanup, LViewBlueprint, MatchesArray, TCleanup, TNodeDebug, TNodeInitialInputs, TNodeLocalNames, TViewComponents, TViewConstructor} from './lview_debug';
|
||||
@ -431,6 +431,10 @@ export function refreshView<T>(
|
||||
}
|
||||
}
|
||||
|
||||
// First mark transplanted views that are declared in this lView as needing a refresh at their
|
||||
// insertion points. This is needed to avoid the situation where the template is defined in this
|
||||
// `LView` but its declaration appears after the insertion component.
|
||||
markTransplantedViewsForRefresh(lView);
|
||||
refreshDynamicEmbeddedViews(lView);
|
||||
|
||||
// Content query results must be refreshed before content hooks are called.
|
||||
@ -507,6 +511,10 @@ export function refreshView<T>(
|
||||
if (!checkNoChangesMode) {
|
||||
lView[FLAGS] &= ~(LViewFlags.Dirty | LViewFlags.FirstLViewPass);
|
||||
}
|
||||
if (lView[FLAGS] & LViewFlags.RefreshTransplantedView) {
|
||||
lView[FLAGS] &= ~LViewFlags.RefreshTransplantedView;
|
||||
updateTransplantedViewCount(lView[PARENT] as LContainer, -1);
|
||||
}
|
||||
} finally {
|
||||
leaveView();
|
||||
}
|
||||
@ -1600,85 +1608,94 @@ export function createLContainer(
|
||||
ActiveIndexFlag.DYNAMIC_EMBEDDED_VIEWS_ONLY << ActiveIndexFlag.SHIFT, // active index
|
||||
currentView, // parent
|
||||
null, // next
|
||||
null, // queries
|
||||
tNode, // t_host
|
||||
native, // native,
|
||||
null, // view refs
|
||||
0, // transplanted views to refresh count
|
||||
tNode, // t_host
|
||||
native, // native,
|
||||
null, // view refs
|
||||
null, // moved views
|
||||
);
|
||||
ngDevMode &&
|
||||
assertEqual(
|
||||
lContainer.length, CONTAINER_HEADER_OFFSET,
|
||||
'Should allocate correct number of slots for LContainer header.');
|
||||
ngDevMode && attachLContainerDebug(lContainer);
|
||||
return lContainer;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Goes over dynamic embedded views (ones created through ViewContainerRef APIs) and refreshes
|
||||
* them by executing an associated template function.
|
||||
*/
|
||||
function refreshDynamicEmbeddedViews(lView: LView) {
|
||||
let viewOrContainer = lView[CHILD_HEAD];
|
||||
while (viewOrContainer !== null) {
|
||||
// Note: viewOrContainer can be an LView or an LContainer instance, but here we are only
|
||||
// interested in LContainer
|
||||
let activeIndexFlag: ActiveIndexFlag;
|
||||
if (isLContainer(viewOrContainer) &&
|
||||
(activeIndexFlag = viewOrContainer[ACTIVE_INDEX]) >> ActiveIndexFlag.SHIFT ===
|
||||
ActiveIndexFlag.DYNAMIC_EMBEDDED_VIEWS_ONLY) {
|
||||
for (let i = CONTAINER_HEADER_OFFSET; i < viewOrContainer.length; i++) {
|
||||
const embeddedLView = viewOrContainer[i] as LView;
|
||||
const embeddedTView = embeddedLView[TVIEW];
|
||||
ngDevMode && assertDefined(embeddedTView, 'TView must be allocated');
|
||||
if (viewAttachedToChangeDetector(embeddedLView)) {
|
||||
refreshView(
|
||||
embeddedTView, embeddedLView, embeddedTView.template, embeddedLView[CONTEXT]!);
|
||||
}
|
||||
}
|
||||
if ((activeIndexFlag & ActiveIndexFlag.HAS_TRANSPLANTED_VIEWS) !== 0) {
|
||||
// We should only CD moved views if the component where they were inserted does not match
|
||||
// the component where they were declared and insertion is on-push. Moved views also
|
||||
// contains intra component moves, or check-always which need to be skipped.
|
||||
refreshTransplantedViews(viewOrContainer, lView[DECLARATION_COMPONENT_VIEW]!);
|
||||
for (let lContainer = getFirstLContainer(lView); lContainer !== null;
|
||||
lContainer = getNextLContainer(lContainer)) {
|
||||
for (let i = CONTAINER_HEADER_OFFSET; i < lContainer.length; i++) {
|
||||
const embeddedLView = lContainer[i];
|
||||
const embeddedTView = embeddedLView[TVIEW];
|
||||
ngDevMode && assertDefined(embeddedTView, 'TView must be allocated');
|
||||
if (viewAttachedToChangeDetector(embeddedLView)) {
|
||||
refreshView(embeddedTView, embeddedLView, embeddedTView.template, embeddedLView[CONTEXT]!);
|
||||
}
|
||||
}
|
||||
viewOrContainer = viewOrContainer[NEXT];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the first `LContainer` in the LView or `null` if none exists.
|
||||
*/
|
||||
function getFirstLContainer(lView: LView): LContainer|null {
|
||||
let viewOrContainer = lView[CHILD_HEAD];
|
||||
while (viewOrContainer !== null &&
|
||||
!(isLContainer(viewOrContainer) &&
|
||||
viewOrContainer[ACTIVE_INDEX] >> ActiveIndexFlag.SHIFT ===
|
||||
ActiveIndexFlag.DYNAMIC_EMBEDDED_VIEWS_ONLY)) {
|
||||
viewOrContainer = viewOrContainer[NEXT];
|
||||
}
|
||||
return viewOrContainer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh transplanted LViews.
|
||||
* Gets the next `LContainer` that is a sibling of the given container.
|
||||
*/
|
||||
function getNextLContainer(container: LContainer): LContainer|null {
|
||||
let viewOrContainer = container[NEXT];
|
||||
while (viewOrContainer !== null &&
|
||||
!(isLContainer(viewOrContainer) &&
|
||||
viewOrContainer[ACTIVE_INDEX] >> ActiveIndexFlag.SHIFT ===
|
||||
ActiveIndexFlag.DYNAMIC_EMBEDDED_VIEWS_ONLY)) {
|
||||
viewOrContainer = viewOrContainer[NEXT];
|
||||
}
|
||||
return viewOrContainer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark transplanted views as needing to be refreshed at their insertion points.
|
||||
*
|
||||
* See: `ActiveIndexFlag.HAS_TRANSPLANTED_VIEWS` and `LView[DECLARATION_COMPONENT_VIEW]` for
|
||||
* explanation of transplanted views.
|
||||
*
|
||||
* @param lContainer The `LContainer` which has transplanted views.
|
||||
* @param declaredComponentLView The `lContainer` parent component `LView`.
|
||||
* @param lView The `LView` that may have transplanted views.
|
||||
*/
|
||||
function refreshTransplantedViews(lContainer: LContainer, declaredComponentLView: LView) {
|
||||
const movedViews = lContainer[MOVED_VIEWS]!;
|
||||
ngDevMode && assertDefined(movedViews, 'Transplanted View flags set but missing MOVED_VIEWS');
|
||||
for (let i = 0; i < movedViews.length; i++) {
|
||||
const movedLView = movedViews[i]!;
|
||||
const insertionLContainer = movedLView[PARENT] as LContainer;
|
||||
ngDevMode && assertLContainer(insertionLContainer);
|
||||
const insertedComponentLView = insertionLContainer[PARENT][DECLARATION_COMPONENT_VIEW]!;
|
||||
ngDevMode && assertDefined(insertedComponentLView, 'Missing LView');
|
||||
// Check if we have a transplanted view by compering declaration and insertion location.
|
||||
if (insertedComponentLView !== declaredComponentLView) {
|
||||
// Yes the `LView` is transplanted.
|
||||
// Here we would like to know if the component is `OnPush`. We don't have
|
||||
// explicit `OnPush` flag instead we set `CheckAlways` to false (which is `OnPush`)
|
||||
// Not to be confused with `ManualOnPush` which is used with wether a DOM event
|
||||
// should automatically mark a view as dirty.
|
||||
const insertionComponentIsOnPush =
|
||||
(insertedComponentLView[FLAGS] & LViewFlags.CheckAlways) === 0;
|
||||
if (insertionComponentIsOnPush) {
|
||||
// Here we know that the template has been transplanted across components and is
|
||||
// on-push (not just moved within a component). If the insertion is marked dirty, then
|
||||
// there is no need to CD here as we will do it again later when we get to insertion
|
||||
// point.
|
||||
const movedTView = movedLView[TVIEW];
|
||||
ngDevMode && assertDefined(movedTView, 'TView must be allocated');
|
||||
refreshView(movedTView, movedLView, movedTView.template, movedLView[CONTEXT]!);
|
||||
function markTransplantedViewsForRefresh(lView: LView) {
|
||||
for (let lContainer = getFirstLContainer(lView); lContainer !== null;
|
||||
lContainer = getNextLContainer(lContainer)) {
|
||||
if ((lContainer[ACTIVE_INDEX] & ActiveIndexFlag.HAS_TRANSPLANTED_VIEWS) !== 0) {
|
||||
const movedViews = lContainer[MOVED_VIEWS]!;
|
||||
ngDevMode && assertDefined(movedViews, 'Transplanted View flags set but missing MOVED_VIEWS');
|
||||
for (let i = 0; i < movedViews.length; i++) {
|
||||
const movedLView = movedViews[i]!;
|
||||
const insertionLContainer = movedLView[PARENT] as LContainer;
|
||||
ngDevMode && assertLContainer(insertionLContainer);
|
||||
// We don't want to increment the counter if the moved LView was already marked for
|
||||
// refresh.
|
||||
if ((movedLView[FLAGS] & LViewFlags.RefreshTransplantedView) === 0) {
|
||||
updateTransplantedViewCount(insertionLContainer, 1);
|
||||
}
|
||||
// Note, it is possible that the `movedViews` is tracking views that are transplanted *and*
|
||||
// those that aren't (declaration component === insertion component). In the latter case,
|
||||
// it's fine to add the flag, as we will clear it immediately in
|
||||
// `refreshDynamicEmbeddedViews` for the view currently being refreshed.
|
||||
movedLView[FLAGS] |= LViewFlags.RefreshTransplantedView;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1695,10 +1712,50 @@ function refreshComponent(hostLView: LView, componentHostIdx: number): void {
|
||||
ngDevMode && assertEqual(isCreationMode(hostLView), false, 'Should be run in update mode');
|
||||
const componentView = getComponentLViewByIndex(componentHostIdx, hostLView);
|
||||
// Only attached components that are CheckAlways or OnPush and dirty should be refreshed
|
||||
if (viewAttachedToChangeDetector(componentView) &&
|
||||
componentView[FLAGS] & (LViewFlags.CheckAlways | LViewFlags.Dirty)) {
|
||||
const componentTView = componentView[TVIEW];
|
||||
refreshView(componentTView, componentView, componentTView.template, componentView[CONTEXT]);
|
||||
if (viewAttachedToChangeDetector(componentView)) {
|
||||
const tView = componentView[TVIEW];
|
||||
if (componentView[FLAGS] & (LViewFlags.CheckAlways | LViewFlags.Dirty)) {
|
||||
refreshView(tView, componentView, tView.template, componentView[CONTEXT]);
|
||||
} else if (componentView[TRANSPLANTED_VIEWS_TO_REFRESH] > 0) {
|
||||
// Only attached components that are CheckAlways or OnPush and dirty should be refreshed
|
||||
refreshContainsDirtyView(componentView);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes all transplanted views marked with `LViewFlags.RefreshTransplantedView` that are
|
||||
* children or descendants of the given lView.
|
||||
*
|
||||
* @param lView The lView which contains descendant transplanted views that need to be refreshed.
|
||||
*/
|
||||
function refreshContainsDirtyView(lView: LView) {
|
||||
for (let lContainer = getFirstLContainer(lView); lContainer !== null;
|
||||
lContainer = getNextLContainer(lContainer)) {
|
||||
for (let i = CONTAINER_HEADER_OFFSET; i < lContainer.length; i++) {
|
||||
const embeddedLView = lContainer[i];
|
||||
if (embeddedLView[FLAGS] & LViewFlags.RefreshTransplantedView) {
|
||||
const embeddedTView = embeddedLView[TVIEW];
|
||||
ngDevMode && assertDefined(embeddedTView, 'TView must be allocated');
|
||||
refreshView(embeddedTView, embeddedLView, embeddedTView.template, embeddedLView[CONTEXT]!);
|
||||
} else if (embeddedLView[TRANSPLANTED_VIEWS_TO_REFRESH] > 0) {
|
||||
refreshContainsDirtyView(embeddedLView);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const tView = lView[TVIEW];
|
||||
// Refresh child component views.
|
||||
const components = tView.components;
|
||||
if (components !== null) {
|
||||
for (let i = 0; i < components.length; i++) {
|
||||
const componentView = getComponentLViewByIndex(components[i], lView);
|
||||
// Only attached components that are CheckAlways or OnPush and dirty should be refreshed
|
||||
if (viewAttachedToChangeDetector(componentView) &&
|
||||
componentView[TRANSPLANTED_VIEWS_TO_REFRESH] > 0) {
|
||||
refreshContainsDirtyView(componentView);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,8 +10,8 @@ import {ViewRef} from '../../linker/view_ref';
|
||||
|
||||
import {TNode} from './node';
|
||||
import {RComment, RElement} from './renderer';
|
||||
import {HOST, LView, NEXT, PARENT, T_HOST, TRANSPLANTED_VIEWS_TO_REFRESH} from './view';
|
||||
|
||||
import {HOST, LView, NEXT, PARENT, T_HOST} from './view';
|
||||
|
||||
|
||||
/**
|
||||
@ -27,16 +27,16 @@ export const TYPE = 1;
|
||||
*/
|
||||
export const ACTIVE_INDEX = 2;
|
||||
|
||||
// PARENT and NEXT are indices 3 and 4
|
||||
// PARENT, NEXT, TRANSPLANTED_VIEWS_TO_REFRESH are indices 3, 4, and 5
|
||||
// As we already have these constants in LView, we don't need to re-create them.
|
||||
|
||||
export const MOVED_VIEWS = 5;
|
||||
|
||||
// T_HOST is index 6
|
||||
// We already have this constants in LView, we don't need to re-create it.
|
||||
|
||||
export const NATIVE = 7;
|
||||
export const VIEW_REFS = 8;
|
||||
export const MOVED_VIEWS = 9;
|
||||
|
||||
|
||||
/**
|
||||
* Size of LContainer's header. Represents the index after which all views in the
|
||||
@ -44,7 +44,7 @@ export const VIEW_REFS = 8;
|
||||
* which views are already in the DOM (and don't need to be re-added) and so we can
|
||||
* remove views from the DOM when they are no longer required.
|
||||
*/
|
||||
export const CONTAINER_HEADER_OFFSET = 9;
|
||||
export const CONTAINER_HEADER_OFFSET = 10;
|
||||
|
||||
|
||||
/**
|
||||
@ -132,6 +132,14 @@ export interface LContainer extends Array<any> {
|
||||
*/
|
||||
[NEXT]: LView|LContainer|null;
|
||||
|
||||
/**
|
||||
* The number of direct transplanted views which need a refresh or have descendants themselves
|
||||
* that need a refresh but have not marked their ancestors as Dirty. This tells us that during
|
||||
* change detection we should still descend to find those children to refresh, even if the parents
|
||||
* are not `Dirty`/`CheckAlways`.
|
||||
*/
|
||||
[TRANSPLANTED_VIEWS_TO_REFRESH]: number;
|
||||
|
||||
/**
|
||||
* A collection of views created based on the underlying `<ng-template>` element but inserted into
|
||||
* a different `LContainer`. We need to track views created from a given declaration point since
|
||||
|
@ -31,7 +31,7 @@ export const TVIEW = 1;
|
||||
export const FLAGS = 2;
|
||||
export const PARENT = 3;
|
||||
export const NEXT = 4;
|
||||
export const QUERIES = 5;
|
||||
export const TRANSPLANTED_VIEWS_TO_REFRESH = 5;
|
||||
export const T_HOST = 6;
|
||||
export const CLEANUP = 7;
|
||||
export const CONTEXT = 8;
|
||||
@ -45,8 +45,9 @@ export const DECLARATION_VIEW = 15;
|
||||
export const DECLARATION_COMPONENT_VIEW = 16;
|
||||
export const DECLARATION_LCONTAINER = 17;
|
||||
export const PREORDER_HOOK_FLAGS = 18;
|
||||
export const QUERIES = 19;
|
||||
/** Size of LView's header. Necessary to adjust for it when setting slots. */
|
||||
export const HEADER_OFFSET = 19;
|
||||
export const HEADER_OFFSET = 20;
|
||||
|
||||
|
||||
// This interface replaces the real LView interface if it is an arg or a
|
||||
@ -282,6 +283,14 @@ export interface LView extends Array<any> {
|
||||
* More flags for this view. See PreOrderHookFlags for more info.
|
||||
*/
|
||||
[PREORDER_HOOK_FLAGS]: PreOrderHookFlags;
|
||||
|
||||
/**
|
||||
* The number of direct transplanted views which need a refresh or have descendants themselves
|
||||
* that need a refresh but have not marked their ancestors as Dirty. This tells us that during
|
||||
* change detection we should still descend to find those children to refresh, even if the parents
|
||||
* are not `Dirty`/`CheckAlways`.
|
||||
*/
|
||||
[TRANSPLANTED_VIEWS_TO_REFRESH]: number;
|
||||
}
|
||||
|
||||
/** Flags associated with an LView (saved in LView[FLAGS]) */
|
||||
@ -342,11 +351,17 @@ export const enum LViewFlags {
|
||||
IsRoot = 0b001000000000,
|
||||
|
||||
/**
|
||||
* Index of the current init phase on last 22 bits
|
||||
* Whether this moved LView was needs to be refreshed at the insertion location because the
|
||||
* declaration was dirty.
|
||||
*/
|
||||
IndexWithinInitPhaseIncrementer = 0b010000000000,
|
||||
IndexWithinInitPhaseShift = 10,
|
||||
IndexWithinInitPhaseReset = 0b001111111111,
|
||||
RefreshTransplantedView = 0b0010000000000,
|
||||
|
||||
/**
|
||||
* Index of the current init phase on last 21 bits
|
||||
*/
|
||||
IndexWithinInitPhaseIncrementer = 0b0100000000000,
|
||||
IndexWithinInitPhaseShift = 11,
|
||||
IndexWithinInitPhaseReset = 0b0011111111111,
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -23,7 +23,7 @@ import {isLContainer, isLView} from './interfaces/type_checks';
|
||||
import {CHILD_HEAD, CLEANUP, DECLARATION_COMPONENT_VIEW, DECLARATION_LCONTAINER, DestroyHookData, FLAGS, HookData, HookFn, HOST, LView, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, T_HOST, TVIEW, TView, unusedValueExportToPlacateAjd as unused5} from './interfaces/view';
|
||||
import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert';
|
||||
import {getLViewParent} from './util/view_traversal_utils';
|
||||
import {getNativeByTNode, unwrapRNode} from './util/view_utils';
|
||||
import {getNativeByTNode, unwrapRNode, updateTransplantedViewCount} from './util/view_utils';
|
||||
|
||||
const unusedValueToPlacateAjd = unused1 + unused2 + unused3 + unused4 + unused5;
|
||||
|
||||
@ -270,18 +270,13 @@ function trackMovedView(declarationContainer: LContainer, lView: LView) {
|
||||
ngDevMode && assertLContainer(insertedLContainer);
|
||||
const insertedComponentLView = insertedLContainer[PARENT]![DECLARATION_COMPONENT_VIEW];
|
||||
ngDevMode && assertDefined(insertedComponentLView, 'Missing insertedComponentLView');
|
||||
const insertedComponentIsOnPush =
|
||||
(insertedComponentLView[FLAGS] & LViewFlags.CheckAlways) !== LViewFlags.CheckAlways;
|
||||
if (insertedComponentIsOnPush) {
|
||||
const declaredComponentLView = lView[DECLARATION_COMPONENT_VIEW];
|
||||
ngDevMode && assertDefined(declaredComponentLView, 'Missing declaredComponentLView');
|
||||
if (declaredComponentLView !== insertedComponentLView) {
|
||||
// At this point the declaration-component is not same as insertion-component and we are in
|
||||
// on-push mode, this means that this is a transplanted view. Mark the declared lView as
|
||||
// having
|
||||
// transplanted views so that those views can participate in CD.
|
||||
declarationContainer[ACTIVE_INDEX] |= ActiveIndexFlag.HAS_TRANSPLANTED_VIEWS;
|
||||
}
|
||||
const declaredComponentLView = lView[DECLARATION_COMPONENT_VIEW];
|
||||
ngDevMode && assertDefined(declaredComponentLView, 'Missing declaredComponentLView');
|
||||
if (declaredComponentLView !== insertedComponentLView) {
|
||||
// At this point the declaration-component is not same as insertion-component; this means that
|
||||
// this is a transplanted view. Mark the declared lView as having transplanted views so that
|
||||
// those views can participate in CD.
|
||||
declarationContainer[ACTIVE_INDEX] |= ActiveIndexFlag.HAS_TRANSPLANTED_VIEWS;
|
||||
}
|
||||
if (movedViews === null) {
|
||||
declarationContainer[MOVED_VIEWS] = [lView];
|
||||
@ -297,8 +292,18 @@ function detachMovedView(declarationContainer: LContainer, lView: LView) {
|
||||
declarationContainer[MOVED_VIEWS],
|
||||
'A projected view should belong to a non-empty projected views collection');
|
||||
const movedViews = declarationContainer[MOVED_VIEWS]!;
|
||||
const declaredViewIndex = movedViews.indexOf(lView);
|
||||
movedViews.splice(declaredViewIndex, 1);
|
||||
const declarationViewIndex = movedViews.indexOf(lView);
|
||||
const insertionLContainer = lView[PARENT] as LContainer;
|
||||
ngDevMode && assertLContainer(insertionLContainer);
|
||||
|
||||
// If the view was marked for refresh but then detached before it was checked (where the flag
|
||||
// would be cleared and the counter decremented), we need to decrement the view counter here
|
||||
// instead.
|
||||
if (lView[FLAGS] & LViewFlags.RefreshTransplantedView) {
|
||||
updateTransplantedViewCount(insertionLContainer, -1);
|
||||
}
|
||||
|
||||
movedViews.splice(declarationViewIndex, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -13,7 +13,7 @@ import {LContext, MONKEY_PATCH_KEY_NAME} from '../interfaces/context';
|
||||
import {TConstants, TNode} from '../interfaces/node';
|
||||
import {isProceduralRenderer, RNode} from '../interfaces/renderer';
|
||||
import {isLContainer, isLView} from '../interfaces/type_checks';
|
||||
import {FLAGS, HEADER_OFFSET, HOST, LView, LViewFlags, PARENT, PREORDER_HOOK_FLAGS, RENDERER, TData, TView} from '../interfaces/view';
|
||||
import {FLAGS, HEADER_OFFSET, HOST, LView, LViewFlags, PARENT, PREORDER_HOOK_FLAGS, RENDERER, TData, TRANSPLANTED_VIEWS_TO_REFRESH, TView} from '../interfaces/view';
|
||||
|
||||
|
||||
|
||||
@ -194,4 +194,24 @@ export function getLContainerActiveIndex(lContainer: LContainer) {
|
||||
|
||||
export function setLContainerActiveIndex(lContainer: LContainer, index: number) {
|
||||
lContainer[ACTIVE_INDEX] = index << ActiveIndexFlag.SHIFT;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the `TRANSPLANTED_VIEWS_TO_REFRESH` counter on the `LContainer` as well as the parents
|
||||
* whose
|
||||
* 1. counter goes from 0 to 1, indicating that there is a new child that has a view to refresh
|
||||
* or
|
||||
* 2. counter goes from 1 to 0, indicating there are no more descendant views to refresh
|
||||
*/
|
||||
export function updateTransplantedViewCount(lContainer: LContainer, amount: 1|- 1) {
|
||||
lContainer[TRANSPLANTED_VIEWS_TO_REFRESH] += amount;
|
||||
let viewOrContainer: LView|LContainer = lContainer;
|
||||
let parent: LView|LContainer|null = lContainer[PARENT];
|
||||
while (parent !== null &&
|
||||
((amount === 1 && viewOrContainer[TRANSPLANTED_VIEWS_TO_REFRESH] === 1) ||
|
||||
(amount === -1 && viewOrContainer[TRANSPLANTED_VIEWS_TO_REFRESH] === 0))) {
|
||||
parent[TRANSPLANTED_VIEWS_TO_REFRESH] += amount;
|
||||
viewOrContainer = parent;
|
||||
parent = parent[PARENT];
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user