Skip to content

feat(cdk-experimental/column-resize): Support column size persistance… #30136

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Dec 10, 2024
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
8 changes: 6 additions & 2 deletions src/cdk-experimental/column-resize/column-resize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ export const COLUMN_RESIZE_OPTIONS = new InjectionToken<ColumnResizeOptions>(
*/
@Directive()
export abstract class ColumnResize implements AfterViewInit, OnDestroy {
private _idGenerator = inject(_IdGenerator);
protected readonly destroyed = new Subject<void>();

/* Publicly accessible interface for triggering and being notified of resizes. */
Expand All @@ -58,7 +57,7 @@ export abstract class ColumnResize implements AfterViewInit, OnDestroy {
protected abstract readonly notifier: ColumnResizeNotifierSource;

/** Unique ID for this table instance. */
protected readonly selectorId = this._idGenerator.getId('cdk-column-resize-');
protected readonly selectorId = inject(_IdGenerator).getId('cdk-column-resize-');

/** The id attribute of the table, if specified. */
id?: string;
Expand Down Expand Up @@ -88,6 +87,11 @@ export abstract class ColumnResize implements AfterViewInit, OnDestroy {
return this.selectorId;
}

/** Gets the ID for this table used for column size persistance. */
getTableId(): string {
return String(this.elementRef.nativeElement.id);
}

/** Called when a column in the table is resized. Applies a css class to the table element. */
setResized() {
this.elementRef.nativeElement!.classList.add(WITH_RESIZED_COLUMN_CLASS);
Expand Down
5 changes: 3 additions & 2 deletions src/cdk-experimental/column-resize/column-size-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,16 @@
*/

import {Injectable} from '@angular/core';
import {Observable} from 'rxjs';

/**
* Can be provided by the host application to enable persistence of column resize state.
*/
@Injectable()
export abstract class ColumnSizeStore {
/** Returns the persisted size of the specified column in the specified table. */
abstract getSize(tableId: string, columnId: string): number;
abstract getSize(tableId: string, columnId: string): Observable<number | null> | null;

/** Persists the size of the specified column in the specified table. */
abstract setSize(tableId: string, columnId: string): void;
abstract setSize(tableId: string, columnId: string, sizePx: number): void;
}
29 changes: 28 additions & 1 deletion src/cdk-experimental/column-resize/resizable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
AfterViewInit,
Directive,
ElementRef,
inject,
Injector,
NgZone,
OnDestroy,
Expand All @@ -22,14 +23,15 @@ import {ComponentPortal} from '@angular/cdk/portal';
import {Overlay, OverlayRef} from '@angular/cdk/overlay';
import {CdkColumnDef, _CoalescedStyleScheduler} from '@angular/cdk/table';
import {merge, Subject} from 'rxjs';
import {filter, takeUntil} from 'rxjs/operators';
import {distinctUntilChanged, filter, take, takeUntil} from 'rxjs/operators';

import {_closest} from '@angular/cdk-experimental/popover-edit';

import {HEADER_ROW_SELECTOR} from './selectors';
import {ResizeOverlayHandle} from './overlay-handle';
import {ColumnResize} from './column-resize';
import {ColumnSizeAction, ColumnResizeNotifierSource} from './column-resize-notifier';
import {ColumnSizeStore} from './column-size-store';
import {HeaderRowEventDispatcher} from './event-dispatcher';
import {ResizeRef} from './resize-ref';
import {ResizeStrategy} from './resize-strategy';
Expand Down Expand Up @@ -66,6 +68,8 @@ export abstract class Resizable<HandleComponent extends ResizeOverlayHandle>
protected abstract readonly viewContainerRef: ViewContainerRef;
protected abstract readonly changeDetectorRef: ChangeDetectorRef;

protected readonly columnSizeStore = inject(ColumnSizeStore, {optional: true});

private _viewInitialized = false;
private _isDestroyed = false;

Expand Down Expand Up @@ -105,6 +109,15 @@ export abstract class Resizable<HandleComponent extends ResizeOverlayHandle>
this._viewInitialized = true;
this._applyMinWidthPx();
this._applyMaxWidthPx();
this.columnSizeStore
?.getSize(this.columnResize.getTableId(), this.columnDef.name)
?.pipe(take(1), takeUntil(this.destroyed))
.subscribe(size => {
if (size == null) {
return;
}
this._applySize(size);
});
});
}

Expand Down Expand Up @@ -195,6 +208,20 @@ export abstract class Resizable<HandleComponent extends ResizeOverlayHandle>
.subscribe(columnSize => {
this._cleanUpAfterResize(columnSize);
});

this.resizeNotifier.resizeCompleted
.pipe(
filter(sizeUpdate => sizeUpdate.columnId === this.columnDef.name),
distinctUntilChanged((a, b) => a.size === b.size),
takeUntil(this.destroyed),
)
.subscribe(sizeUpdate => {
this.columnSizeStore?.setSize(
this.columnResize.getTableId(),
this.columnDef.name,
sizeUpdate.size,
);
});
}

private _completeResizeOperation(): void {
Expand Down
109 changes: 104 additions & 5 deletions src/material-experimental/column-resize/column-resize.spec.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
import {BidiModule} from '@angular/cdk/bidi';
import {DataSource} from '@angular/cdk/collections';
import {ESCAPE} from '@angular/cdk/keycodes';
import {ChangeDetectionStrategy, Component, Directive, ElementRef, ViewChild} from '@angular/core';
import {
ChangeDetectionStrategy,
Component,
Directive,
ElementRef,
Injectable,
ViewChild,
} from '@angular/core';
import {ComponentFixture, TestBed, fakeAsync, flush} from '@angular/core/testing';
import {MatTableModule} from '@angular/material/table';
import {BehaviorSubject} from 'rxjs';
import {BehaviorSubject, Observable, ReplaySubject} from 'rxjs';
import {dispatchKeyboardEvent} from '../../cdk/testing/private';

import {ColumnSize} from '@angular/cdk-experimental/column-resize';
import {ColumnSize, ColumnSizeStore} from '@angular/cdk-experimental/column-resize';
import {AbstractMatColumnResize} from './column-resize-directives/common';
import {
MatColumnResize,
Expand Down Expand Up @@ -52,7 +59,7 @@ function getTableTemplate(defaultEnabled: boolean) {
}
</style>
<div #table [dir]="direction">
<table ${directives.table} mat-table [dataSource]="dataSource"
<table ${directives.table} mat-table [dataSource]="dataSource" id="theTable"
style="table-layout: fixed;">
<!-- Position Column -->
<ng-container matColumnDef="position" sticky>
Expand Down Expand Up @@ -109,7 +116,7 @@ function getFlexTemplate(defaultEnabled: boolean) {
}
</style>
<div #table [dir]="direction">
<mat-table ${directives.table} [dataSource]="dataSource">
<mat-table ${directives.table} [dataSource]="dataSource" id="theTable">
<!-- Position Column -->
<ng-container matColumnDef="position" sticky>
<mat-header-cell *matHeaderCellDef
Expand Down Expand Up @@ -628,6 +635,63 @@ describe('Material Popover Edit', () => {
}));
});
}

describe('ColumnSizeStore (persistance)', () => {
let component: BaseTestComponent;
let fixture: ComponentFixture<BaseTestComponent>;
let columnSizeStore: FakeColumnSizeStore;

beforeEach(fakeAsync(() => {
jasmine.addMatchers(approximateMatcher);

TestBed.configureTestingModule({
imports: [BidiModule, MatTableModule, MatColumnResizeModule],
providers: [
FakeColumnSizeStore,
{provide: ColumnSizeStore, useExisting: FakeColumnSizeStore},
],
declarations: [MatResizeOnPushTest],
});
fixture = TestBed.createComponent(MatResizeOnPushTest);
component = fixture.componentInstance;
columnSizeStore = TestBed.inject(FakeColumnSizeStore);
fixture.detectChanges();
flush();
}));

it('applies the persisted size', fakeAsync(() => {
(expect(component.getColumnWidth(1)).not as any).isApproximately(300);

columnSizeStore.emitSize('theTable', 'name', 300);

flush();

(expect(component.getColumnWidth(1)) as any).isApproximately(300);
}));

it('persists the user-triggered size update', fakeAsync(() => {
const initialColumnWidth = component.getColumnWidth(1);

component.triggerHoverState();
fixture.detectChanges();

component.resizeColumnWithMouse(1, 5);
fixture.detectChanges();
flush();

component.completeResizeWithMouseInProgress(1);
flush();

component.endHoverState();
fixture.detectChanges();

expect(columnSizeStore.setSizeCalls.length).toBe(1);
const {tableId, columnId, sizePx} = columnSizeStore.setSizeCalls[0];
expect(tableId).toBe('theTable');
expect(columnId).toBe('name');
(expect(sizePx) as any).isApproximately(initialColumnWidth + 5);
}));
});
});

function createElementData() {
Expand All @@ -639,3 +703,38 @@ function createElementData() {
{position: 5, name: 'Boron', weight: 10.811, symbol: 'B'},
];
}

@Injectable()
class FakeColumnSizeStore extends ColumnSizeStore {
readonly emitStore = new Map<string, ReplaySubject<number>>();
readonly setSizeCalls: {tableId: string; columnId: string; sizePx: number}[] = [];

/** Returns an observable that will emit values from emitSize(). */
override getSize(tableId: string, columnId: string): Observable<number> | null {
return this._getOrAdd(tableId, columnId);
}

/**
* Adds an entry to setSizeCalls.
* Note: Does not affect values returned from getSize.
*/
override setSize(tableId: string, columnId: string, sizePx: number): void {
this.setSizeCalls.push({tableId, columnId, sizePx});
}

/** Call this in test code to simulate persisted column sizes. */
emitSize(tableId: string, columnId: string, sizePx: number) {
const stored = this._getOrAdd(tableId, columnId);
stored.next(sizePx);
}

private _getOrAdd(tableId: string, columnId: string): ReplaySubject<number> {
const key = `tableId----columnId`;
let stored = this.emitStore.get(key);
if (!stored) {
stored = new ReplaySubject<number>(1);
this.emitStore.set(key, stored);
}
return stored;
}
}
2 changes: 1 addition & 1 deletion src/material-experimental/column-resize/public-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,4 @@ export * from './resizable-directives/resizable';
export * from './resize-strategy';
export * from './overlay-handle';
export type {ColumnResizeOptions} from '@angular/cdk-experimental/column-resize';
export {COLUMN_RESIZE_OPTIONS} from '@angular/cdk-experimental/column-resize';
export {COLUMN_RESIZE_OPTIONS, ColumnSizeStore} from '@angular/cdk-experimental/column-resize';
Loading