diff --git a/packages/core/src/view/types.ts b/packages/core/src/view/types.ts
index 46811ab273..8746da9c4d 100644
--- a/packages/core/src/view/types.ts
+++ b/packages/core/src/view/types.ts
@@ -329,9 +329,10 @@ export const enum ViewState {
FirstCheck = 1 << 1,
Attached = 1 << 2,
ChecksEnabled = 1 << 3,
- CheckProjectedView = 1 << 4,
- CheckProjectedViews = 1 << 5,
- Destroyed = 1 << 6,
+ IsProjectedView = 1 << 4,
+ CheckProjectedView = 1 << 5,
+ CheckProjectedViews = 1 << 6,
+ Destroyed = 1 << 7,
CatDetectChanges = Attached | ChecksEnabled,
CatInit = BeforeFirstCheck | CatDetectChanges
diff --git a/packages/core/src/view/view_attach.ts b/packages/core/src/view/view_attach.ts
index a25af16337..31726e3139 100644
--- a/packages/core/src/view/view_attach.ts
+++ b/packages/core/src/view/view_attach.ts
@@ -18,19 +18,7 @@ export function attachEmbeddedView(
}
view.viewContainerParent = parentView;
addToArray(embeddedViews, viewIndex !, view);
- const dvcElementData = declaredViewContainer(view);
- if (dvcElementData && dvcElementData !== elementData) {
- let projectedViews = dvcElementData.template._projectedViews;
- if (!projectedViews) {
- projectedViews = dvcElementData.template._projectedViews = [];
- }
- projectedViews.push(view);
- if (view.parent) {
- // Note: we are changing the NodeDef here as we cannot calculate
- // the fact whether a template is used for projection during compilation.
- markNodeAsProjectedTemplate(view.parent.def, view.parentNodeDef !);
- }
- }
+ attachProjectedView(elementData, view);
Services.dirtyParentQueries(view);
@@ -38,6 +26,30 @@ export function attachEmbeddedView(
renderAttachEmbeddedView(elementData, prevView, view);
}
+function attachProjectedView(vcElementData: ElementData, view: ViewData) {
+ const dvcElementData = declaredViewContainer(view);
+ if (!dvcElementData || dvcElementData === vcElementData ||
+ view.state & ViewState.IsProjectedView) {
+ return;
+ }
+ // Note: For performance reasons, we
+ // - add a view to template._projectedViews only 1x throughout its lifetime,
+ // and remove it not until the view is destroyed.
+ // (hard, as when a parent view is attached/detached we would need to attach/detach all
+ // nested projected views as well, even accross component boundaries).
+ // - don't track the insertion order of views in the projected views array
+ // (hard, as when the views of the same template are inserted different view containers)
+ view.state |= ViewState.IsProjectedView;
+ let projectedViews = dvcElementData.template._projectedViews;
+ if (!projectedViews) {
+ projectedViews = dvcElementData.template._projectedViews = [];
+ }
+ projectedViews.push(view);
+ // Note: we are changing the NodeDef here as we cannot calculate
+ // the fact whether a template is used for projection during compilation.
+ markNodeAsProjectedTemplate(view.parent !.def, view.parentNodeDef !);
+}
+
function markNodeAsProjectedTemplate(viewDef: ViewDefinition, nodeDef: NodeDef) {
if (nodeDef.flags & NodeFlags.ProjectedTemplate) {
return;
@@ -63,12 +75,7 @@ export function detachEmbeddedView(elementData: ElementData, viewIndex?: number)
view.viewContainerParent = null;
removeFromArray(embeddedViews, viewIndex);
- const dvcElementData = declaredViewContainer(view);
- if (dvcElementData && dvcElementData !== elementData) {
- const projectedViews = dvcElementData.template._projectedViews;
- removeFromArray(projectedViews, projectedViews.indexOf(view));
- }
-
+ // See attachProjectedView for why we don't update projectedViews here.
Services.dirtyParentQueries(view);
renderDetachView(view);
@@ -76,6 +83,20 @@ export function detachEmbeddedView(elementData: ElementData, viewIndex?: number)
return view;
}
+export function detachProjectedView(view: ViewData) {
+ if (!(view.state & ViewState.IsProjectedView)) {
+ return;
+ }
+ const dvcElementData = declaredViewContainer(view);
+ if (dvcElementData) {
+ const projectedViews = dvcElementData.template._projectedViews;
+ if (projectedViews) {
+ removeFromArray(projectedViews, projectedViews.indexOf(view));
+ Services.dirtyParentQueries(view);
+ }
+ }
+}
+
export function moveEmbeddedView(
elementData: ElementData, oldViewIndex: number, newViewIndex: number): ViewData {
const embeddedViews = elementData.viewContainer !._embeddedViews;
diff --git a/packages/core/test/linker/query_integration_spec.ts b/packages/core/test/linker/query_integration_spec.ts
index 1b18ab716a..92b8fa0047 100644
--- a/packages/core/test/linker/query_integration_spec.ts
+++ b/packages/core/test/linker/query_integration_spec.ts
@@ -536,6 +536,52 @@ export function main() {
expect(q.query.length).toBe(0);
});
+ // Note: This tests is just document our current behavior, which we do
+ // for performance reasons.
+ it('should not affected queries for projected templates if views are detached or moved', () => {
+ const template =
+ '