fix(core): projected views should be dirty checked when the declaring component is dirty checked. (#16592)

Previously a projected view was only dirty checked when the
component in which it was inserted was dirty checked.

This fix changes the behavior so that a view is also dirty checked if
the declaring component is dirty checked.

Note: This does not change the order of change detection,
only the fact whether a projected view is dirty checked or not.

Fixes #14321
This commit is contained in:
Tobias Bosch
2017-05-05 13:49:59 -07:00
committed by Miško Hevery
parent 3887d8a429
commit fcc91d862f
5 changed files with 246 additions and 39 deletions

View File

@ -17,7 +17,8 @@ import {checkAndUpdateQuery, createQuery} from './query';
import {createTemplateData, createViewContainerData} from './refs';
import {checkAndUpdateTextDynamic, checkAndUpdateTextInline, createText} from './text';
import {ArgumentType, CheckType, ElementData, NodeData, NodeDef, NodeFlags, ProviderData, RootData, Services, ViewData, ViewDefinition, ViewFlags, ViewHandleEventFn, ViewState, ViewUpdateFn, asElementData, asQueryList, asTextData} from './types';
import {NOOP, checkBindingNoChanges, isComponentView, resolveViewDefinition} from './util';
import {NOOP, checkBindingNoChanges, isComponentView, markParentViewsForCheckProjectedViews, resolveViewDefinition} from './util';
import {detachProjectedView} from './view_attach';
export function viewDef(
flags: ViewFlags, nodes: NodeDef[], updateDirectives?: ViewUpdateFn,
@ -314,12 +315,14 @@ function createViewNodes(view: ViewData) {
}
export function checkNoChangesView(view: ViewData) {
markProjectedViewsForCheck(view);
Services.updateDirectives(view, CheckType.CheckNoChanges);
execEmbeddedViewsAction(view, ViewAction.CheckNoChanges);
Services.updateRenderer(view, CheckType.CheckNoChanges);
execComponentViewsAction(view, ViewAction.CheckNoChanges);
// Note: We don't check queries for changes as we didn't do this in v2.x.
// TODO(tbosch): investigate if we can enable the check again in v5.x with a nicer error message.
view.state &= ~(ViewState.CheckProjectedViews | ViewState.CheckProjectedView);
}
export function checkAndUpdateView(view: ViewData) {
@ -329,6 +332,7 @@ export function checkAndUpdateView(view: ViewData) {
} else {
view.state &= ~ViewState.FirstCheck;
}
markProjectedViewsForCheck(view);
Services.updateDirectives(view, CheckType.CheckAndUpdate);
execEmbeddedViewsAction(view, ViewAction.CheckAndUpdate);
execQueriesAction(
@ -343,7 +347,6 @@ export function checkAndUpdateView(view: ViewData) {
execComponentViewsAction(view, ViewAction.CheckAndUpdate);
execQueriesAction(
view, NodeFlags.TypeViewQuery, NodeFlags.DynamicQuery, CheckType.CheckAndUpdate);
callLifecycleHooksChildrenFirst(
view, NodeFlags.AfterViewChecked |
(view.state & ViewState.FirstCheck ? NodeFlags.AfterViewInit : 0));
@ -351,6 +354,7 @@ export function checkAndUpdateView(view: ViewData) {
if (view.def.flags & ViewFlags.OnPush) {
view.state &= ~ViewState.ChecksEnabled;
}
view.state &= ~(ViewState.CheckProjectedViews | ViewState.CheckProjectedView);
}
export function checkAndUpdateNode(
@ -363,6 +367,31 @@ export function checkAndUpdateNode(
}
}
function markProjectedViewsForCheck(view: ViewData) {
const def = view.def;
if (!(def.nodeFlags & NodeFlags.ProjectedTemplate)) {
return;
}
for (let i = 0; i < def.nodes.length; i++) {
const nodeDef = def.nodes[i];
if (nodeDef.flags & NodeFlags.ProjectedTemplate) {
const projectedViews = asElementData(view, i).template._projectedViews;
if (projectedViews) {
for (let i = 0; i < projectedViews.length; i++) {
const projectedView = projectedViews[i];
projectedView.state |= ViewState.CheckProjectedView;
markParentViewsForCheckProjectedViews(projectedView, view);
}
}
} else if ((nodeDef.childFlags & NodeFlags.ProjectedTemplate) === 0) {
// a parent with leafs
// no child is a component,
// then skip the children
i += nodeDef.childCount;
}
}
}
function checkAndUpdateNodeInline(
view: ViewData, nodeDef: NodeDef, v0?: any, v1?: any, v2?: any, v3?: any, v4?: any, v5?: any,
v6?: any, v7?: any, v8?: any, v9?: any): boolean {
@ -474,6 +503,7 @@ export function destroyView(view: ViewData) {
view.disposables[i]();
}
}
detachProjectedView(view);
if (view.renderer.destroyNode) {
destroyViewNodes(view);
}
@ -498,7 +528,9 @@ function destroyViewNodes(view: ViewData) {
enum ViewAction {
CreateViewNodes,
CheckNoChanges,
CheckNoChangesProjectedViews,
CheckAndUpdate,
CheckAndUpdateProjectedViews,
Destroy
}
@ -547,18 +579,44 @@ function callViewAction(view: ViewData, action: ViewAction) {
const viewState = view.state;
switch (action) {
case ViewAction.CheckNoChanges:
if ((viewState & ViewState.CatDetectChanges) === ViewState.CatDetectChanges &&
(viewState & ViewState.Destroyed) === 0) {
checkNoChangesView(view);
if ((viewState & ViewState.Destroyed) === 0) {
if ((viewState & ViewState.CatDetectChanges) === ViewState.CatDetectChanges) {
checkNoChangesView(view);
} else if (viewState & ViewState.CheckProjectedViews) {
execProjectedViewsAction(view, ViewAction.CheckNoChangesProjectedViews);
}
}
break;
case ViewAction.CheckNoChangesProjectedViews:
if ((viewState & ViewState.Destroyed) === 0) {
if (viewState & ViewState.CheckProjectedView) {
checkNoChangesView(view);
} else if (viewState & ViewState.CheckProjectedViews) {
execProjectedViewsAction(view, action);
}
}
break;
case ViewAction.CheckAndUpdate:
if ((viewState & ViewState.CatDetectChanges) === ViewState.CatDetectChanges &&
(viewState & ViewState.Destroyed) === 0) {
checkAndUpdateView(view);
if ((viewState & ViewState.Destroyed) === 0) {
if ((viewState & ViewState.CatDetectChanges) === ViewState.CatDetectChanges) {
checkAndUpdateView(view);
} else if (viewState & ViewState.CheckProjectedViews) {
execProjectedViewsAction(view, ViewAction.CheckAndUpdateProjectedViews);
}
}
break;
case ViewAction.CheckAndUpdateProjectedViews:
if ((viewState & ViewState.Destroyed) === 0) {
if (viewState & ViewState.CheckProjectedView) {
checkAndUpdateView(view);
} else if (viewState & ViewState.CheckProjectedViews) {
execProjectedViewsAction(view, action);
}
}
break;
case ViewAction.Destroy:
// Note: destroyView recurses over all views,
// so we don't need to special case projected views here.
destroyView(view);
break;
case ViewAction.CreateViewNodes:
@ -567,6 +625,11 @@ function callViewAction(view: ViewData, action: ViewAction) {
}
}
function execProjectedViewsAction(view: ViewData, action: ViewAction) {
execEmbeddedViewsAction(view, action);
execComponentViewsAction(view, action);
}
function execQueriesAction(
view: ViewData, queryFlags: NodeFlags, staticDynamicQueryFlag: NodeFlags,
checkType: CheckType) {