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) { -