diff --git a/projects/igniteui-angular-elements/src/app/custom-strategy.spec.ts b/projects/igniteui-angular-elements/src/app/custom-strategy.spec.ts
index d5ec22f100e..15428665123 100644
--- a/projects/igniteui-angular-elements/src/app/custom-strategy.spec.ts
+++ b/projects/igniteui-angular-elements/src/app/custom-strategy.spec.ts
@@ -1,7 +1,9 @@
+import { ApplicationRef, ViewContainerRef } from '@angular/core';
import { IgxActionStripComponent, IgxColumnComponent, IgxGridComponent, IgxHierarchicalGridComponent, PivotGridType } from 'igniteui-angular';
import { html } from 'lit';
import { firstValueFrom, fromEvent, timer } from 'rxjs';
import { ComponentRefKey, IgcNgElement } from './custom-strategy';
+import { injector } from '../utils/injector-ref';
import hgridData from '../assets/data/projects-hgrid.js';
import { SampleTestData } from 'igniteui-angular/test-utils/sample-test-data.spec';
import {
@@ -15,6 +17,11 @@ import {
IgcActionStripComponent,
IgcGridEditingActionsComponent,
IgcPivotDataSelectorComponent,
+ IgcGridToolbarComponent,
+ IgcGridToolbarActionsComponent,
+ IgcGridToolbarTitleComponent,
+ IgcGridToolbarPinningComponent,
+ IgcGridToolbarHidingComponent,
} from './components';
import { defineComponents } from '../utils/register';
@@ -32,7 +39,12 @@ describe('Elements: ', () => {
IgcPaginatorComponent,
IgcGridStateComponent,
IgcActionStripComponent,
- IgcGridEditingActionsComponent
+ IgcGridEditingActionsComponent,
+ IgcGridToolbarComponent,
+ IgcGridToolbarActionsComponent,
+ IgcGridToolbarTitleComponent,
+ IgcGridToolbarPinningComponent,
+ IgcGridToolbarHidingComponent
);
});
@@ -371,6 +383,128 @@ describe('Elements: ', () => {
expect(actionStrip.isConnected).toBeTrue();
});
+ it('should attach nested elements into the parent view instead of as separate application roots', async () => {
+ testContainer.innerHTML = `
+
+
+ Title
+
+
+
+
+
+
+
+
+ `;
+
+ const gridEl = document.querySelector>('#testGrid')!;
+
+ await firstValueFrom(fromEvent(gridEl, "childrenResolved"));
+
+ const elementOf = (selector: string) =>
+ selector === 'igc-grid' ? gridEl : gridEl.querySelector(selector);
+ const hostViewOf = async (selector: string) =>
+ (await (elementOf(selector))?.ngElementStrategy[ComponentRefKey])?.hostView;
+ // the view container each element inserts its children's views into
+ const anchorOf = async (selector: string) =>
+ (await (elementOf(selector))?.ngElementStrategy[ComponentRefKey])?.injector.get(ViewContainerRef);
+ // views the ApplicationRef ticks directly; anything else is reached through its parent
+ const rootViews = (injector.get(ApplicationRef) as any)._views as unknown[];
+
+ // no element parent to attach to, so the grid stays a root
+ expect(rootViews.includes(await hostViewOf('igc-grid'))).toBeTrue();
+
+ // each nested element's view lives in its parent element's container, not in the app's roots
+ const expectedParents = {
+ 'igc-grid-toolbar': 'igc-grid',
+ 'igc-grid-toolbar-title': 'igc-grid-toolbar',
+ 'igc-grid-toolbar-actions': 'igc-grid-toolbar',
+ 'igc-grid-toolbar-hiding': 'igc-grid-toolbar-actions',
+ 'igc-grid-toolbar-pinning': 'igc-grid-toolbar-actions',
+ 'igc-column': 'igc-grid',
+ 'igc-paginator': 'igc-grid'
+ };
+
+ for (const [selector, parentSelector] of Object.entries(expectedParents)) {
+ const hostView = await hostViewOf(selector);
+ expect(rootViews.includes(hostView))
+ .withContext(`${selector} should not be attached as a separate root view`).toBeFalse();
+ expect((await anchorOf(parentSelector))?.indexOf(hostView!))
+ .withContext(`${selector} should be attached in the view of ${parentSelector}`)
+ .toBeGreaterThan(-1);
+ }
+ });
+
+ it('should preserve the DOM position of nested elements when attaching them to the parent view', async () => {
+ // the attach moves the element next to the parent's host element, so it has to be put back
+ testContainer.innerHTML = `
+
+
+ Title
+
+
+
+
+
+
+
+
+ `;
+
+ const gridEl = document.querySelector>('#testGrid')!;
+
+ await firstValueFrom(fromEvent(gridEl, "childrenResolved"));
+
+ // nothing stranded next to the grid, where the insert temporarily moves elements
+ expect(Array.from(testContainer.children).map(x => x.tagName)).toEqual(['IGC-GRID']);
+
+ const toolbarEl = gridEl.querySelector('igc-grid-toolbar');
+ const actionsEl = gridEl.querySelector('igc-grid-toolbar-actions');
+ const paginatorEl = gridEl.querySelector('igc-paginator');
+
+ expect(toolbarEl?.parentElement).toBe(gridEl);
+ expect(toolbarEl?.querySelector('igc-grid-toolbar-title')?.parentElement).toBe(toolbarEl);
+ expect(actionsEl?.parentElement).toBe(toolbarEl);
+ expect(Array.from(gridEl?.querySelectorAll('igc-column') || []).every(x => x.parentElement === gridEl)).toBeTrue();
+
+ // sibling order kept as authored
+ expect(Array.from(actionsEl?.children || []).map(x => x.tagName))
+ .toEqual(['IGC-GRID-TOOLBAR-HIDING', 'IGC-GRID-TOOLBAR-PINNING']);
+
+ // the paginator is projected deeper (into the footer) - that spot survives the attach too
+ expect(gridEl?.contains(paginatorEl)).toBeTrue();
+ expect(paginatorEl?.parentElement).not.toBe(gridEl);
+ });
+
+ it('should refresh a nested toolbar action when only the parent grid is marked for check', async () => {
+ testContainer.innerHTML = `
+
+
+
+
+
+
+
+
+ `;
+
+ const gridEl = document.querySelector>('#testGrid')!;
+
+ await firstValueFrom(fromEvent(gridEl, "childrenResolved"));
+
+ const pinnedCount = () => gridEl.querySelector('igc-grid-toolbar-pinning span')?.textContent.trim();
+ expect(pinnedCount()).toEqual('0');
+
+ gridEl.pinColumn('ProductID');
+ await firstValueFrom(timer(10 /* SCHEDULE_DELAY */ * 2));
+ expect(pinnedCount()).toEqual('1');
+
+ gridEl.unpinColumn('ProductID');
+ await firstValueFrom(timer(10 /* SCHEDULE_DELAY */ * 2));
+ expect(pinnedCount()).toEqual('0');
+ });
+
it('should update the UI correctly after invoking a method', async () => {
// Regression coverage for UI updates after removing the zone.js dependency.
const gridEl = document.createElement("igc-grid");
diff --git a/projects/igniteui-angular-elements/src/app/custom-strategy.ts b/projects/igniteui-angular-elements/src/app/custom-strategy.ts
index 4f06b415140..dd27277d4e3 100644
--- a/projects/igniteui-angular-elements/src/app/custom-strategy.ts
+++ b/projects/igniteui-angular-elements/src/app/custom-strategy.ts
@@ -132,17 +132,10 @@ class IgxCustomNgElementStrategy extends ComponentNgElementStrategy {
if (parent?.ngElementStrategy) {
this.angularParent = parent.ngElementStrategy.angularParent;
this.parentElement = new WeakRef(parent);
- let parentComponentRef = await parent?.ngElementStrategy[ComponentRefKey];
+ const parentComponentRef = await parent?.ngElementStrategy[ComponentRefKey];
parentInjector = parentComponentRef?.injector;
-
- // TODO: Consider general solution (as in Parent w/ @igxAnchor tag)
- if (element.tagName.toLocaleLowerCase() === 'igc-grid-toolbar'
- || element.tagName.toLocaleLowerCase() === 'igc-paginator') {
- // NOPE: viewcontainerRef will re-render this node again, no option for rootNode :S
- // this.componentRef = parentAnchor.createComponent(this.componentFactory.componentType, { projectableNodes, injector: childInjector });
- parentComponentRef = await parent?.ngElementStrategy[ComponentRefKey];
- parentAnchor = parentComponentRef?.instance.anchor;
- }
+ // Use anchor to attach to the parent's view tree instead of a standalone root.
+ parentAnchor = parentInjector.get(ViewContainerRef);
} else if ((parent as any)?.__componentRef) {
this.angularParent = (parent as any).__componentRef;
parentInjector = this.angularParent.injector;
@@ -197,9 +190,12 @@ class IgxCustomNgElementStrategy extends ComponentNgElementStrategy {
// const parentViewRef = parentInjector.get(ViewContainerRef);
// preserve original position in DOM (in case of projection, e.g. grid pager):
const domParent = element.parentElement;
- const nextSibling = element.nextSibling;
- parentAnchor.insert((this as any).componentRef.hostView); //bad, moves in DOM, AND need to be in inner anchor :S
- //restore original DOM position
+ // `insert` moves all root nodes & some components have more than one,
+ // so a potential `nextSibling` is always the one after the _last_ root node.
+ const nextSibling = (this as any).componentRef.hostView.rootNodes.at(-1).nextSibling;
+ parentAnchor.insert((this as any).componentRef.hostView);
+ // only the view hierarchy is wanted here, so undo the DOM move `insert` does
+ // and restore original DOM position
domParent!.insertBefore(element, nextSibling);
(this as any).componentRef.hostView.detectChanges();
} else if (!parentAnchor) {
diff --git a/projects/igniteui-angular-elements/src/lib/grids/row-island.component.ts b/projects/igniteui-angular-elements/src/lib/grids/row-island.component.ts
index 0b9d752e8b7..21b7911cc55 100644
--- a/projects/igniteui-angular-elements/src/lib/grids/row-island.component.ts
+++ b/projects/igniteui-angular-elements/src/lib/grids/row-island.component.ts
@@ -26,10 +26,10 @@ import { IgxActionStripToken } from 'igniteui-angular/core';
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
selector: 'igx-row-island',
- template: `
+ template: `
-
`,
+ `,
providers: [
IgxRowIslandAPIService,
IgxFilteringService,
diff --git a/projects/igniteui-angular/grids/grid/src/grid-base.directive.ts b/projects/igniteui-angular/grids/grid/src/grid-base.directive.ts
index 1204bbabbc0..db13936f250 100644
--- a/projects/igniteui-angular/grids/grid/src/grid-base.directive.ts
+++ b/projects/igniteui-angular/grids/grid/src/grid-base.directive.ts
@@ -1754,13 +1754,6 @@ export abstract class IgxGridBaseDirective implements GridType,
@ViewChild('igxFilteringOverlayOutlet', { read: IgxOverlayOutletDirective, static: true })
protected _outletDirective!: IgxOverlayOutletDirective;
- /**
- * @hidden @internal
- * @igxElementsAnchor
- */
- @ViewChild('sink', { read: ViewContainerRef, static: true })
- public anchor!: ViewContainerRef;
-
/**
* @hidden @internal
*/
diff --git a/projects/igniteui-angular/grids/grid/src/grid.component.html b/projects/igniteui-angular/grids/grid/src/grid.component.html
index 2c59086caba..6fa4152190c 100644
--- a/projects/igniteui-angular/grids/grid/src/grid.component.html
+++ b/projects/igniteui-angular/grids/grid/src/grid.component.html
@@ -323,7 +323,6 @@
}
@if (platform.isElements) {
-
}
diff --git a/projects/igniteui-angular/grids/hierarchical-grid/src/hierarchical-grid.component.html b/projects/igniteui-angular/grids/hierarchical-grid/src/hierarchical-grid.component.html
index d7b99fdf073..5c3961f357c 100644
--- a/projects/igniteui-angular/grids/hierarchical-grid/src/hierarchical-grid.component.html
+++ b/projects/igniteui-angular/grids/hierarchical-grid/src/hierarchical-grid.component.html
@@ -274,7 +274,6 @@
}
@if (platform.isElements) {
-
diff --git a/projects/igniteui-angular/grids/pivot-grid/src/pivot-grid.component.html b/projects/igniteui-angular/grids/pivot-grid/src/pivot-grid.component.html
index e60cfcea708..10171dc629f 100644
--- a/projects/igniteui-angular/grids/pivot-grid/src/pivot-grid.component.html
+++ b/projects/igniteui-angular/grids/pivot-grid/src/pivot-grid.component.html
@@ -231,6 +231,5 @@
@if (platform.isElements) {
-
}
diff --git a/projects/igniteui-angular/grids/tree-grid/src/tree-grid.component.html b/projects/igniteui-angular/grids/tree-grid/src/tree-grid.component.html
index 15ef80849c6..55d1131661e 100644
--- a/projects/igniteui-angular/grids/tree-grid/src/tree-grid.component.html
+++ b/projects/igniteui-angular/grids/tree-grid/src/tree-grid.component.html
@@ -574,7 +574,6 @@
}
@if (platform.isElements) {
-