Skip to content

Commit

Permalink
feat(cdk-experimental/column-resize): Support column size persistance…
Browse files Browse the repository at this point in the history
… hooks (#30136)
  • Loading branch information
kseamon authored Dec 10, 2024
1 parent 4a0818d commit 75c8aa8
Show file tree
Hide file tree
Showing 5 changed files with 142 additions and 11 deletions.
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';

0 comments on commit 75c8aa8

Please sign in to comment.