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 d056d5c67af..c5619dd3743 100644 --- a/projects/igniteui-angular-elements/src/app/custom-strategy.spec.ts +++ b/projects/igniteui-angular-elements/src/app/custom-strategy.spec.ts @@ -14,6 +14,9 @@ import { IgcColumnLayoutComponent, IgcActionStripComponent, IgcGridEditingActionsComponent, + IgcGridToolbarComponent, + IgcGridToolbarActionsComponent, + IgcGridToolbarHidingComponent, } from './components'; import { defineComponents } from '../utils/register'; @@ -30,7 +33,10 @@ describe('Elements: ', () => { IgcPaginatorComponent, IgcGridStateComponent, IgcActionStripComponent, - IgcGridEditingActionsComponent + IgcGridEditingActionsComponent, + IgcGridToolbarComponent, + IgcGridToolbarActionsComponent, + IgcGridToolbarHidingComponent ); }); @@ -365,5 +371,49 @@ describe('Elements: ', () => { highlightedCell = gridEl.querySelector(HIGHLIGHT_ACTIVE_CSS_CLASS); expect(highlightedCell).toBeNull(); }); + + it('should update the open column hiding dropdown when a column is removed', async () => { + // Removing a column resets the QueryList with no click and no element insert + const gridEl = document.createElement("igc-grid"); + const toolbar = document.createElement("igc-grid-toolbar"); + const actions = document.createElement("igc-grid-toolbar-actions"); + const hiding = document.createElement("igc-grid-toolbar-hiding"); + actions.appendChild(hiding); + toolbar.appendChild(actions); + gridEl.appendChild(toolbar); + + const columns = ["ProductID", "ProductName", "InStock"].map(field => { + const col = document.createElement("igc-column"); + col.setAttribute("field", field); + gridEl.appendChild(col); + return col; + }); + + gridEl.data = SampleTestData.foodProductData(); + testContainer.appendChild(gridEl); + + await firstValueFrom(fromEvent(gridEl, "childrenResolved")); + await firstValueFrom(fromEvent(gridEl, "dataChanged")); + + const listedColumns = () => + document.querySelectorAll('igx-column-actions .igx-column-actions__columns-item').length; + // Grid init timing varies, so poll instead of guessing a fixed delay + const waitForListed = async (expected: number) => { + for (let waited = 0; waited < 3000 && listedColumns() !== expected; waited += 20) { + await firstValueFrom(timer(20)); + } + }; + + hiding.querySelector('button').click(); + await waitForListed(3); + expect(listedColumns()).toBe(3); + + const resolved = firstValueFrom(fromEvent(gridEl, "childrenResolved")); + gridEl.removeChild(columns[2]); + await resolved; + + await waitForListed(2); + expect(listedColumns()).toBe(2); + }); }); }); diff --git a/projects/igniteui-angular/grids/core/src/column-actions/column-actions.component.ts b/projects/igniteui-angular/grids/core/src/column-actions/column-actions.component.ts index 61de8da3ee9..4361888b871 100644 --- a/projects/igniteui-angular/grids/core/src/column-actions/column-actions.component.ts +++ b/projects/igniteui-angular/grids/core/src/column-actions/column-actions.component.ts @@ -15,8 +15,12 @@ import { forwardRef, inject, ChangeDetectionStrategy, + ChangeDetectorRef, + DestroyRef, + OnInit, ViewEncapsulation } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { ColumnDisplayOrder } from '../common/enums'; import { GridType } from '../common/grid.interface'; import { IColumnToggledEventArgs } from '../common/events'; @@ -42,8 +46,10 @@ let NEXT_ID = 0; changeDetection: ChangeDetectionStrategy.Eager, imports: [IgxInputGroupComponent, FormsModule, IgxInputDirective, IgxCheckboxComponent, IgxButtonDirective, IgxRippleDirective, forwardRef(() => IgxColumnActionEnabledPipe), forwardRef(() => IgxFilterActionColumnsPipe), forwardRef(() => IgxSortActionColumnsPipe)] }) -export class IgxColumnActionsComponent implements DoCheck { +export class IgxColumnActionsComponent implements DoCheck, OnInit { private differs = inject(IterableDiffers); + private cdr = inject(ChangeDetectorRef); + private destroyRef = inject(DestroyRef); /** @@ -187,6 +193,19 @@ export class IgxColumnActionsComponent implements DoCheck { this._differ = this.differs.find([]).create(this.trackChanges); } + /** + * @hidden @internal + */ + public ngOnInit() { + // ngDoCheck only runs if something else checks this view, which is not guaranteed + this.grid?.columnList?.changes + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe(() => { + this.pipeTrigger++; + this.cdr.markForCheck(); + }); + } + /** * Gets the prompt that is displayed in the filter input. * diff --git a/projects/igniteui-angular/grids/core/src/toolbar/grid-toolbar-advanced-filtering.component.ts b/projects/igniteui-angular/grids/core/src/toolbar/grid-toolbar-advanced-filtering.component.ts index 6db1ccde0ce..5ef71a8762c 100644 --- a/projects/igniteui-angular/grids/core/src/toolbar/grid-toolbar-advanced-filtering.component.ts +++ b/projects/igniteui-angular/grids/core/src/toolbar/grid-toolbar-advanced-filtering.component.ts @@ -1,4 +1,5 @@ -import { Component, Input, OnInit, inject, ChangeDetectionStrategy } from '@angular/core'; +import { Component, Input, OnInit, inject, ChangeDetectionStrategy, ChangeDetectorRef, DestroyRef } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { IgxToolbarToken } from './token'; import { IgxButtonDirective, IgxRippleDirective } from 'igniteui-angular/directives'; import { IgxIconComponent } from 'igniteui-angular/icon'; @@ -31,6 +32,8 @@ import { IFilteringExpressionsTree, isTree, OverlaySettings } from 'igniteui-ang }) export class IgxGridToolbarAdvancedFilteringComponent implements OnInit { private toolbar = inject(IgxToolbarToken); + private cdr = inject(ChangeDetectorRef); + private destroyRef = inject(DestroyRef); protected numberOfColumns!: number; /** @@ -52,9 +55,13 @@ export class IgxGridToolbarAdvancedFilteringComponent implements OnInit { this.numberOfColumns = this.grid?.advancedFilteringExpressionsTree ? this.extractUniqueFieldNamesFromFilterTree(this.grid?.advancedFilteringExpressionsTree).length : 0; // Subscribing for future updates - this.grid?.advancedFilteringExpressionsTreeChange.subscribe(filteringTree => { - this.numberOfColumns = this.extractUniqueFieldNamesFromFilterTree(filteringTree).length; - }); + this.grid?.advancedFilteringExpressionsTreeChange + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe(filteringTree => { + this.numberOfColumns = this.extractUniqueFieldNamesFromFilterTree(filteringTree).length; + // The dialog changes the tree outside a check of this view + this.cdr.markForCheck(); + }); } protected extractUniqueFieldNamesFromFilterTree(filteringTree?: IFilteringExpressionsTree) : string[] { diff --git a/projects/igniteui-angular/grids/core/src/toolbar/grid-toolbar.base.ts b/projects/igniteui-angular/grids/core/src/toolbar/grid-toolbar.base.ts index 9d8baf24809..df56a33e00b 100644 --- a/projects/igniteui-angular/grids/core/src/toolbar/grid-toolbar.base.ts +++ b/projects/igniteui-angular/grids/core/src/toolbar/grid-toolbar.base.ts @@ -1,4 +1,5 @@ -import { Directive, Input, EventEmitter, OnDestroy, Output, booleanAttribute, inject } from '@angular/core'; +import { Directive, Input, EventEmitter, OnDestroy, OnInit, Output, booleanAttribute, inject, ChangeDetectorRef, DestroyRef } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { Subject, Subscription } from 'rxjs'; import { first, takeUntil } from 'rxjs/operators'; @@ -163,7 +164,10 @@ export abstract class BaseToolbarDirective implements OnDestroy { * Base class for pinning/hiding column actions */ @Directive() -export abstract class BaseToolbarColumnActionsDirective extends BaseToolbarDirective { +export abstract class BaseToolbarColumnActionsDirective extends BaseToolbarDirective implements OnInit { + private cdr = inject(ChangeDetectorRef); + private destroyRef = inject(DestroyRef); + @Input({ transform: booleanAttribute }) public hideFilter = false; @@ -190,6 +194,14 @@ export abstract class BaseToolbarColumnActionsDirective extends BaseToolbarDirec protected columnActionsUI!: IgxColumnActionsComponent; + /** @hidden @internal */ + public ngOnInit() { + // The counts change from an action that never checks this view + const markDirty = () => this.cdr.markForCheck(); + this.grid?.columnPinned.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(markDirty); + this.grid?.columnVisibilityChanged.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(markDirty); + } + public checkAll() { this.columnActionsUI.checkAllColumns(); }