Skip to content

Commit 9218812

Browse files
tboschjasonaden
authored andcommitted
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
1 parent ec77bf9 commit 9218812

File tree

5 files changed

+246
-39
lines changed

5 files changed

+246
-39
lines changed

packages/core/src/view/types.ts

Lines changed: 31 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -141,37 +141,38 @@ export const enum NodeFlags {
141141
None = 0,
142142
TypeElement = 1 << 0,
143143
TypeText = 1 << 1,
144+
ProjectedTemplate = 1 << 2,
144145
CatRenderNode = TypeElement | TypeText,
145-
TypeNgContent = 1 << 2,
146-
TypePipe = 1 << 3,
147-
TypePureArray = 1 << 4,
148-
TypePureObject = 1 << 5,
149-
TypePurePipe = 1 << 6,
146+
TypeNgContent = 1 << 3,
147+
TypePipe = 1 << 4,
148+
TypePureArray = 1 << 5,
149+
TypePureObject = 1 << 6,
150+
TypePurePipe = 1 << 7,
150151
CatPureExpression = TypePureArray | TypePureObject | TypePurePipe,
151-
TypeValueProvider = 1 << 7,
152-
TypeClassProvider = 1 << 8,
153-
TypeFactoryProvider = 1 << 9,
154-
TypeUseExistingProvider = 1 << 10,
155-
LazyProvider = 1 << 11,
156-
PrivateProvider = 1 << 12,
157-
TypeDirective = 1 << 13,
158-
Component = 1 << 14,
152+
TypeValueProvider = 1 << 8,
153+
TypeClassProvider = 1 << 9,
154+
TypeFactoryProvider = 1 << 10,
155+
TypeUseExistingProvider = 1 << 11,
156+
LazyProvider = 1 << 12,
157+
PrivateProvider = 1 << 13,
158+
TypeDirective = 1 << 14,
159+
Component = 1 << 15,
159160
CatProvider = TypeValueProvider | TypeClassProvider | TypeFactoryProvider |
160161
TypeUseExistingProvider | TypeDirective,
161-
OnInit = 1 << 15,
162-
OnDestroy = 1 << 16,
163-
DoCheck = 1 << 17,
164-
OnChanges = 1 << 18,
165-
AfterContentInit = 1 << 19,
166-
AfterContentChecked = 1 << 20,
167-
AfterViewInit = 1 << 21,
168-
AfterViewChecked = 1 << 22,
169-
EmbeddedViews = 1 << 23,
170-
ComponentView = 1 << 24,
171-
TypeContentQuery = 1 << 25,
172-
TypeViewQuery = 1 << 26,
173-
StaticQuery = 1 << 27,
174-
DynamicQuery = 1 << 28,
162+
OnInit = 1 << 16,
163+
OnDestroy = 1 << 17,
164+
DoCheck = 1 << 18,
165+
OnChanges = 1 << 19,
166+
AfterContentInit = 1 << 20,
167+
AfterContentChecked = 1 << 21,
168+
AfterViewInit = 1 << 22,
169+
AfterViewChecked = 1 << 23,
170+
EmbeddedViews = 1 << 24,
171+
ComponentView = 1 << 25,
172+
TypeContentQuery = 1 << 26,
173+
TypeViewQuery = 1 << 27,
174+
StaticQuery = 1 << 28,
175+
DynamicQuery = 1 << 29,
175176
CatQuery = TypeContentQuery | TypeViewQuery,
176177

177178
// mutually exclusive values...
@@ -328,7 +329,9 @@ export const enum ViewState {
328329
FirstCheck = 1 << 1,
329330
Attached = 1 << 2,
330331
ChecksEnabled = 1 << 3,
331-
Destroyed = 1 << 4,
332+
CheckProjectedView = 1 << 4,
333+
CheckProjectedViews = 1 << 5,
334+
Destroyed = 1 << 6,
332335

333336
CatDetectChanges = Attached | ChecksEnabled,
334337
CatInit = BeforeFirstCheck | CatDetectChanges

packages/core/src/view/util.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,14 @@ export function markParentViewsForCheck(view: ViewData) {
117117
}
118118
}
119119

120+
export function markParentViewsForCheckProjectedViews(view: ViewData, endView: ViewData) {
121+
let currView: ViewData|null = view;
122+
while (currView && currView !== endView) {
123+
currView.state |= ViewState.CheckProjectedViews;
124+
currView = currView.viewContainerParent || currView.parent;
125+
}
126+
}
127+
120128
export function dispatchEvent(
121129
view: ViewData, nodeIndex: number, eventName: string, event: any): boolean {
122130
const nodeDef = view.def.nodes[nodeIndex];

packages/core/src/view/view.ts

Lines changed: 71 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ import {checkAndUpdateQuery, createQuery} from './query';
1717
import {createTemplateData, createViewContainerData} from './refs';
1818
import {checkAndUpdateTextDynamic, checkAndUpdateTextInline, createText} from './text';
1919
import {ArgumentType, CheckType, ElementData, NodeData, NodeDef, NodeFlags, ProviderData, RootData, Services, ViewData, ViewDefinition, ViewFlags, ViewHandleEventFn, ViewState, ViewUpdateFn, asElementData, asQueryList, asTextData} from './types';
20-
import {NOOP, checkBindingNoChanges, isComponentView, resolveViewDefinition} from './util';
20+
import {NOOP, checkBindingNoChanges, isComponentView, markParentViewsForCheckProjectedViews, resolveViewDefinition} from './util';
21+
import {detachProjectedView} from './view_attach';
2122

2223
export function viewDef(
2324
flags: ViewFlags, nodes: NodeDef[], updateDirectives?: ViewUpdateFn,
@@ -314,12 +315,14 @@ function createViewNodes(view: ViewData) {
314315
}
315316

316317
export function checkNoChangesView(view: ViewData) {
318+
markProjectedViewsForCheck(view);
317319
Services.updateDirectives(view, CheckType.CheckNoChanges);
318320
execEmbeddedViewsAction(view, ViewAction.CheckNoChanges);
319321
Services.updateRenderer(view, CheckType.CheckNoChanges);
320322
execComponentViewsAction(view, ViewAction.CheckNoChanges);
321323
// Note: We don't check queries for changes as we didn't do this in v2.x.
322324
// TODO(tbosch): investigate if we can enable the check again in v5.x with a nicer error message.
325+
view.state &= ~(ViewState.CheckProjectedViews | ViewState.CheckProjectedView);
323326
}
324327

325328
export function checkAndUpdateView(view: ViewData) {
@@ -329,6 +332,7 @@ export function checkAndUpdateView(view: ViewData) {
329332
} else {
330333
view.state &= ~ViewState.FirstCheck;
331334
}
335+
markProjectedViewsForCheck(view);
332336
Services.updateDirectives(view, CheckType.CheckAndUpdate);
333337
execEmbeddedViewsAction(view, ViewAction.CheckAndUpdate);
334338
execQueriesAction(
@@ -343,14 +347,14 @@ export function checkAndUpdateView(view: ViewData) {
343347
execComponentViewsAction(view, ViewAction.CheckAndUpdate);
344348
execQueriesAction(
345349
view, NodeFlags.TypeViewQuery, NodeFlags.DynamicQuery, CheckType.CheckAndUpdate);
346-
347350
callLifecycleHooksChildrenFirst(
348351
view, NodeFlags.AfterViewChecked |
349352
(view.state & ViewState.FirstCheck ? NodeFlags.AfterViewInit : 0));
350353

351354
if (view.def.flags & ViewFlags.OnPush) {
352355
view.state &= ~ViewState.ChecksEnabled;
353356
}
357+
view.state &= ~(ViewState.CheckProjectedViews | ViewState.CheckProjectedView);
354358
}
355359

356360
export function checkAndUpdateNode(
@@ -363,6 +367,31 @@ export function checkAndUpdateNode(
363367
}
364368
}
365369

370+
function markProjectedViewsForCheck(view: ViewData) {
371+
const def = view.def;
372+
if (!(def.nodeFlags & NodeFlags.ProjectedTemplate)) {
373+
return;
374+
}
375+
for (let i = 0; i < def.nodes.length; i++) {
376+
const nodeDef = def.nodes[i];
377+
if (nodeDef.flags & NodeFlags.ProjectedTemplate) {
378+
const projectedViews = asElementData(view, i).template._projectedViews;
379+
if (projectedViews) {
380+
for (let i = 0; i < projectedViews.length; i++) {
381+
const projectedView = projectedViews[i];
382+
projectedView.state |= ViewState.CheckProjectedView;
383+
markParentViewsForCheckProjectedViews(projectedView, view);
384+
}
385+
}
386+
} else if ((nodeDef.childFlags & NodeFlags.ProjectedTemplate) === 0) {
387+
// a parent with leafs
388+
// no child is a component,
389+
// then skip the children
390+
i += nodeDef.childCount;
391+
}
392+
}
393+
}
394+
366395
function checkAndUpdateNodeInline(
367396
view: ViewData, nodeDef: NodeDef, v0?: any, v1?: any, v2?: any, v3?: any, v4?: any, v5?: any,
368397
v6?: any, v7?: any, v8?: any, v9?: any): boolean {
@@ -474,6 +503,7 @@ export function destroyView(view: ViewData) {
474503
view.disposables[i]();
475504
}
476505
}
506+
detachProjectedView(view);
477507
if (view.renderer.destroyNode) {
478508
destroyViewNodes(view);
479509
}
@@ -498,7 +528,9 @@ function destroyViewNodes(view: ViewData) {
498528
enum ViewAction {
499529
CreateViewNodes,
500530
CheckNoChanges,
531+
CheckNoChangesProjectedViews,
501532
CheckAndUpdate,
533+
CheckAndUpdateProjectedViews,
502534
Destroy
503535
}
504536

@@ -547,18 +579,44 @@ function callViewAction(view: ViewData, action: ViewAction) {
547579
const viewState = view.state;
548580
switch (action) {
549581
case ViewAction.CheckNoChanges:
550-
if ((viewState & ViewState.CatDetectChanges) === ViewState.CatDetectChanges &&
551-
(viewState & ViewState.Destroyed) === 0) {
552-
checkNoChangesView(view);
582+
if ((viewState & ViewState.Destroyed) === 0) {
583+
if ((viewState & ViewState.CatDetectChanges) === ViewState.CatDetectChanges) {
584+
checkNoChangesView(view);
585+
} else if (viewState & ViewState.CheckProjectedViews) {
586+
execProjectedViewsAction(view, ViewAction.CheckNoChangesProjectedViews);
587+
}
588+
}
589+
break;
590+
case ViewAction.CheckNoChangesProjectedViews:
591+
if ((viewState & ViewState.Destroyed) === 0) {
592+
if (viewState & ViewState.CheckProjectedView) {
593+
checkNoChangesView(view);
594+
} else if (viewState & ViewState.CheckProjectedViews) {
595+
execProjectedViewsAction(view, action);
596+
}
553597
}
554598
break;
555599
case ViewAction.CheckAndUpdate:
556-
if ((viewState & ViewState.CatDetectChanges) === ViewState.CatDetectChanges &&
557-
(viewState & ViewState.Destroyed) === 0) {
558-
checkAndUpdateView(view);
600+
if ((viewState & ViewState.Destroyed) === 0) {
601+
if ((viewState & ViewState.CatDetectChanges) === ViewState.CatDetectChanges) {
602+
checkAndUpdateView(view);
603+
} else if (viewState & ViewState.CheckProjectedViews) {
604+
execProjectedViewsAction(view, ViewAction.CheckAndUpdateProjectedViews);
605+
}
606+
}
607+
break;
608+
case ViewAction.CheckAndUpdateProjectedViews:
609+
if ((viewState & ViewState.Destroyed) === 0) {
610+
if (viewState & ViewState.CheckProjectedView) {
611+
checkAndUpdateView(view);
612+
} else if (viewState & ViewState.CheckProjectedViews) {
613+
execProjectedViewsAction(view, action);
614+
}
559615
}
560616
break;
561617
case ViewAction.Destroy:
618+
// Note: destroyView recurses over all views,
619+
// so we don't need to special case projected views here.
562620
destroyView(view);
563621
break;
564622
case ViewAction.CreateViewNodes:
@@ -567,6 +625,11 @@ function callViewAction(view: ViewData, action: ViewAction) {
567625
}
568626
}
569627

628+
function execProjectedViewsAction(view: ViewData, action: ViewAction) {
629+
execEmbeddedViewsAction(view, action);
630+
execComponentViewsAction(view, action);
631+
}
632+
570633
function execQueriesAction(
571634
view: ViewData, queryFlags: NodeFlags, staticDynamicQueryFlag: NodeFlags,
572635
checkType: CheckType) {

packages/core/src/view/view_attach.ts

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import {ElementData, Services, ViewData} from './types';
10-
import {RenderNodeAction, declaredViewContainer, renderNode, visitRootRenderNodes} from './util';
9+
import {ElementData, NodeDef, NodeFlags, Services, ViewData, ViewDefinition, ViewState} from './types';
10+
import {RenderNodeAction, declaredViewContainer, isComponentView, renderNode, visitRootRenderNodes} from './util';
1111

1212
export function attachEmbeddedView(
1313
parentView: ViewData, elementData: ElementData, viewIndex: number | undefined | null,
@@ -25,6 +25,11 @@ export function attachEmbeddedView(
2525
projectedViews = dvcElementData.template._projectedViews = [];
2626
}
2727
projectedViews.push(view);
28+
if (view.parent) {
29+
// Note: we are changing the NodeDef here as we cannot calculate
30+
// the fact whether a template is used for projection during compilation.
31+
markNodeAsProjectedTemplate(view.parent.def, view.parentNodeDef !);
32+
}
2833
}
2934

3035
Services.dirtyParentQueries(view);
@@ -33,6 +38,19 @@ export function attachEmbeddedView(
3338
renderAttachEmbeddedView(elementData, prevView, view);
3439
}
3540

41+
function markNodeAsProjectedTemplate(viewDef: ViewDefinition, nodeDef: NodeDef) {
42+
if (nodeDef.flags & NodeFlags.ProjectedTemplate) {
43+
return;
44+
}
45+
viewDef.nodeFlags |= NodeFlags.ProjectedTemplate;
46+
nodeDef.flags |= NodeFlags.ProjectedTemplate;
47+
let parentNodeDef = nodeDef.parent;
48+
while (parentNodeDef) {
49+
parentNodeDef.childFlags |= NodeFlags.ProjectedTemplate;
50+
parentNodeDef = parentNodeDef.parent;
51+
}
52+
}
53+
3654
export function detachEmbeddedView(elementData: ElementData, viewIndex?: number): ViewData|null {
3755
const embeddedViews = elementData.viewContainer !._embeddedViews;
3856
if (viewIndex == null || viewIndex >= embeddedViews.length) {

0 commit comments

Comments
 (0)