Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 135 additions & 1 deletion projects/igniteui-angular-elements/src/app/custom-strategy.spec.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -15,6 +17,11 @@ import {
IgcActionStripComponent,
IgcGridEditingActionsComponent,
IgcPivotDataSelectorComponent,
IgcGridToolbarComponent,
IgcGridToolbarActionsComponent,
IgcGridToolbarTitleComponent,
IgcGridToolbarPinningComponent,
IgcGridToolbarHidingComponent,
} from './components';
import { defineComponents } from '../utils/register';

Expand All @@ -32,7 +39,12 @@ describe('Elements: ', () => {
IgcPaginatorComponent,
IgcGridStateComponent,
IgcActionStripComponent,
IgcGridEditingActionsComponent
IgcGridEditingActionsComponent,
IgcGridToolbarComponent,
IgcGridToolbarActionsComponent,
IgcGridToolbarTitleComponent,
IgcGridToolbarPinningComponent,
IgcGridToolbarHidingComponent
);
});

Expand Down Expand Up @@ -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 = `
<igc-grid id="testGrid">
<igc-grid-toolbar>
<igc-grid-toolbar-title>Title</igc-grid-toolbar-title>
<igc-grid-toolbar-actions>
<igc-grid-toolbar-hiding></igc-grid-toolbar-hiding>
<igc-grid-toolbar-pinning></igc-grid-toolbar-pinning>
</igc-grid-toolbar-actions>
</igc-grid-toolbar>
<igc-column field="ProductID"></igc-column>
<igc-column field="ProductName"></igc-column>
<igc-paginator per-page="5"></igc-paginator>
</igc-grid>`;

const gridEl = document.querySelector<IgcNgElement & InstanceType<typeof IgcGridComponent>>('#testGrid')!;

await firstValueFrom(fromEvent(gridEl, "childrenResolved"));

const elementOf = (selector: string) =>
selector === 'igc-grid' ? gridEl : gridEl.querySelector<IgcNgElement>(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 = `
<igc-grid id="testGrid">
<igc-grid-toolbar>
<igc-grid-toolbar-title>Title</igc-grid-toolbar-title>
<igc-grid-toolbar-actions>
<igc-grid-toolbar-hiding></igc-grid-toolbar-hiding>
<igc-grid-toolbar-pinning></igc-grid-toolbar-pinning>
</igc-grid-toolbar-actions>
</igc-grid-toolbar>
<igc-column field="ProductID"></igc-column>
<igc-column field="ProductName"></igc-column>
<igc-paginator per-page="5"></igc-paginator>
</igc-grid>`;

const gridEl = document.querySelector<IgcNgElement & InstanceType<typeof IgcGridComponent>>('#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<HTMLElement>('igc-grid-toolbar');
const actionsEl = gridEl.querySelector<HTMLElement>('igc-grid-toolbar-actions');
const paginatorEl = gridEl.querySelector<HTMLElement>('igc-paginator');

expect(toolbarEl?.parentElement).toBe(gridEl);
expect(toolbarEl?.querySelector<HTMLElement>('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 = `
<igc-grid id="testGrid">
<igc-grid-toolbar>
<igc-grid-toolbar-actions>
<igc-grid-toolbar-pinning></igc-grid-toolbar-pinning>
</igc-grid-toolbar-actions>
</igc-grid-toolbar>
<igc-column field="ProductID"></igc-column>
<igc-column field="ProductName"></igc-column>
</igc-grid>`;

const gridEl = document.querySelector<IgcNgElement & InstanceType<typeof IgcGridComponent>>('#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");
Expand Down
22 changes: 9 additions & 13 deletions projects/igniteui-angular-elements/src/app/custom-strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -197,9 +190,12 @@ class IgxCustomNgElementStrategy extends ComponentNgElementStrategy {
// const parentViewRef = parentInjector.get<ViewContainerRef>(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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@ import { IgxActionStripToken } from 'igniteui-angular/core';
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
selector: 'igx-row-island',
template: `<div #sink style="display: none;">
template: `
<ng-content select="igx-column,igc-column,igx-column-group,igc-column-group,igx-action-strip,igc-action-strip"></ng-content>
<ng-content select="igx-row-island,igc-row-island"></ng-content>
</div>`,
`,
providers: [
IgxRowIslandAPIService,
IgxFilteringService,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,6 @@
}
<div class="igx-grid__outlet" #igxFilteringOverlayOutlet igxOverlayOutlet></div>
@if (platform.isElements) {
<div #sink style="display: none;"></div>
<ng-content select="igx-grid-state,igc-grid-state"></ng-content>
<ng-content select="igx-column,igc-column,igx-column-group,igc-column-group,igx-column-layout,igc-column-layout,igx-action-strip,igc-action-strip"></ng-content>
}
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,6 @@
}
<div class="igx-grid__outlet" #igxFilteringOverlayOutlet igxOverlayOutlet></div>
@if (platform.isElements) {
<div #sink style="display: none;"></div>
<ng-content select="igx-grid-state,igc-grid-state"></ng-content>
<ng-content select="igx-column,igc-column,igx-column-group,igc-column-group,igx-action-strip,igc-action-strip"></ng-content>
<ng-content select="igx-row-island,igc-row-island"></ng-content>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,5 @@
</div>

@if (platform.isElements) {
<div #sink style="display: none;"></div>
<ng-content select="igx-grid-state,igc-grid-state"></ng-content>
}
Original file line number Diff line number Diff line change
Expand Up @@ -574,7 +574,6 @@
}
<div class="igx-grid__outlet" #igxFilteringOverlayOutlet igxOverlayOutlet></div>
@if (platform.isElements) {
<div #sink style="display: none"></div>
<ng-content select="igx-grid-state,igc-grid-state"></ng-content>
<ng-content
select="igx-column,igc-column,igx-column-group,igc-column-group,igx-action-strip,igc-action-strip"
Expand Down
Loading