Skip to content

Commit 120b05c

Browse files
committed
fix(table): Measure column width for sticky columns after new data has rendered.
1 parent a801603 commit 120b05c

File tree

6 files changed

+53
-4
lines changed

6 files changed

+53
-4
lines changed

src/cdk/table/table.spec.ts

+2
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,7 @@ describe('CdkTable', () => {
353353
it('should be able to project a caption', fakeAsync(() => {
354354
setupTableTestApp(NativeHtmlTableWithCaptionApp);
355355
fixture.detectChanges();
356+
flush();
356357

357358
const caption = tableElement.querySelector('caption');
358359

@@ -363,6 +364,7 @@ describe('CdkTable', () => {
363364
it('should be able to project colgroup and col', fakeAsync(() => {
364365
setupTableTestApp(NativeHtmlTableWithColgroupAndCol);
365366
fixture.detectChanges();
367+
flush();
366368

367369
const colgroupsAndCols = Array.from(tableElement.querySelectorAll('colgroup, col'));
368370

src/cdk/table/table.ts

+21-1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import {
4040
IterableChangeRecord,
4141
IterableDiffer,
4242
IterableDiffers,
43+
NgZone,
4344
OnDestroy,
4445
OnInit,
4546
Optional,
@@ -525,6 +526,12 @@ export class CdkTable<T> implements AfterContentChecked, CollectionViewer, OnDes
525526
@SkipSelf()
526527
@Inject(STICKY_POSITIONING_LISTENER)
527528
protected readonly _stickyPositioningListener: StickyPositioningListener,
529+
/**
530+
* @deprecated `_ngZone` parameter to become required.
531+
* @breaking-change 14.0.0
532+
*/
533+
@Optional()
534+
protected readonly _ngZone: NgZone,
528535
) {
529536
if (!role) {
530537
this._elementRef.nativeElement.setAttribute('role', 'table');
@@ -666,7 +673,20 @@ export class CdkTable<T> implements AfterContentChecked, CollectionViewer, OnDes
666673
});
667674

668675
this._updateNoDataRow();
669-
this.updateStickyColumnStyles();
676+
677+
// Allow the new row data to render before measuring it.
678+
// TODO: Remove indirection once _ngZone is required.
679+
const deferredUpdateStickyColumnStyles = () => {
680+
setTimeout(() => {
681+
this.updateStickyColumnStyles();
682+
}, 0);
683+
};
684+
685+
if (this._ngZone) {
686+
this._ngZone.runOutsideAngular(deferredUpdateStickyColumnStyles);
687+
} else {
688+
deferredUpdateStickyColumnStyles();
689+
}
670690

671691
this.contentChanged.next();
672692
}

src/material-experimental/column-resize/column-resize.spec.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import {Component, Directive, ElementRef, ViewChild, ChangeDetectionStrategy} from '@angular/core';
2-
import {ComponentFixture, TestBed, fakeAsync, flushMicrotasks} from '@angular/core/testing';
2+
import {ComponentFixture, TestBed, fakeAsync, flush, flushMicrotasks} from '@angular/core/testing';
33
import {BidiModule} from '@angular/cdk/bidi';
44
import {DataSource} from '@angular/cdk/collections';
55
import {dispatchKeyboardEvent} from '../../cdk/testing/private';
@@ -367,6 +367,7 @@ describe('Material Popover Edit', () => {
367367
component = fixture.componentInstance;
368368
fixture.detectChanges();
369369
flushMicrotasks();
370+
flush();
370371
}));
371372

372373
it('shows resize handle overlays on header row hover and while a resize handle is in use', fakeAsync(() => {

src/material-experimental/mdc-table/table.spec.ts

+11
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
waitForAsync,
44
ComponentFixture,
55
fakeAsync,
6+
flush,
67
flushMicrotasks,
78
TestBed,
89
tick,
@@ -214,6 +215,7 @@ describe('MDC-based MatTable', () => {
214215

215216
const stuckCellElement = fixture.nativeElement.querySelector('table th')!;
216217
expect(stuckCellElement.classList).toContain('mat-mdc-table-sticky');
218+
flush();
217219
}));
218220

219221
// Note: needs to be fakeAsync so it catches the error.
@@ -242,6 +244,7 @@ describe('MDC-based MatTable', () => {
242244
beforeEach(fakeAsync(() => {
243245
fixture = TestBed.createComponent(ArrayDataSourceMatTableApp);
244246
fixture.detectChanges();
247+
flush();
245248

246249
tableElement = fixture.nativeElement.querySelector('table');
247250
component = fixture.componentInstance;
@@ -322,6 +325,7 @@ describe('MDC-based MatTable', () => {
322325
['a_1', 'b_1', 'c_1'],
323326
['Footer A', 'Footer B', 'Footer C'],
324327
]);
328+
flush();
325329

326330
flushMicrotasks(); // Resolve promise that updates paginator's length
327331
expect(dataSource.paginator!.length).toBe(1);
@@ -336,6 +340,7 @@ describe('MDC-based MatTable', () => {
336340
['a_2', 'b_2', 'c_2'],
337341
['Footer A', 'Footer B', 'Footer C'],
338342
]);
343+
flush();
339344

340345
// Change filter to empty string, should match all rows
341346
dataSource.filter = '';
@@ -351,6 +356,7 @@ describe('MDC-based MatTable', () => {
351356
['a_3', 'b_3', 'c_3'],
352357
['Footer A', 'Footer B', 'Footer C'],
353358
]);
359+
flush();
354360

355361
// Change filter function and filter, should match to rows with zebra.
356362
dataSource.filterPredicate = (data, filter) => {
@@ -378,6 +384,7 @@ describe('MDC-based MatTable', () => {
378384
['a_2', 'b_2', 'c_2'],
379385
['Footer A', 'Footer B', 'Footer C'],
380386
]);
387+
flush();
381388

382389
// Change the filter to a falsy value that might come in from the view.
383390
dataSource.filter = 0 as any;
@@ -386,6 +393,7 @@ describe('MDC-based MatTable', () => {
386393
['Column A', 'Column B', 'Column C'],
387394
['Footer A', 'Footer B', 'Footer C'],
388395
]);
396+
flush();
389397
}));
390398

391399
it('should not match concatenated words', fakeAsync(() => {
@@ -398,6 +406,7 @@ describe('MDC-based MatTable', () => {
398406
['Column A', 'Column B', 'Column C'],
399407
['Footer A', 'Footer B', 'Footer C'],
400408
]);
409+
flush();
401410
}));
402411

403412
it('should be able to sort the table contents', () => {
@@ -547,6 +556,7 @@ describe('MDC-based MatTable', () => {
547556
['a_5', 'b_5', 'c_5'],
548557
['Footer A', 'Footer B', 'Footer C'],
549558
]);
559+
flush();
550560

551561
// Navigate to the next page
552562
component.paginator.nextPage();
@@ -560,6 +570,7 @@ describe('MDC-based MatTable', () => {
560570
['a_10', 'b_10', 'c_10'],
561571
['Footer A', 'Footer B', 'Footer C'],
562572
]);
573+
flush();
563574
}));
564575

565576
it('should sort strings with numbers larger than MAX_SAFE_INTEGER correctly', () => {

src/material/table/table.spec.ts

+12
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
waitForAsync,
55
ComponentFixture,
66
fakeAsync,
7+
flush,
78
flushMicrotasks,
89
TestBed,
910
tick,
@@ -203,6 +204,7 @@ describe('MatTable', () => {
203204
const fixture = TestBed.createComponent(StickyTableApp);
204205
fixture.detectChanges();
205206
flushMicrotasks();
207+
flush();
206208

207209
const stuckCellElement = fixture.nativeElement.querySelector('.mat-table th')!;
208210
expect(stuckCellElement.classList).toContain('mat-table-sticky');
@@ -227,6 +229,7 @@ describe('MatTable', () => {
227229
beforeEach(fakeAsync(() => {
228230
fixture = TestBed.createComponent(ArrayDataSourceMatTableApp);
229231
fixture.detectChanges();
232+
flush();
230233

231234
tableElement = fixture.nativeElement.querySelector('.mat-table');
232235
component = fixture.componentInstance;
@@ -300,13 +303,15 @@ describe('MatTable', () => {
300303
// Change filter to a_1, should match one row
301304
dataSource.filter = 'a_1';
302305
fixture.detectChanges();
306+
flush();
303307
expect(dataSource.filteredData.length).toBe(1);
304308
expect(dataSource.filteredData[0]).toBe(dataSource.data[0]);
305309
expectTableToMatchContent(tableElement, [
306310
['Column A', 'Column B', 'Column C'],
307311
['a_1', 'b_1', 'c_1'],
308312
['Footer A', 'Footer B', 'Footer C'],
309313
]);
314+
flush();
310315

311316
flushMicrotasks(); // Resolve promise that updates paginator's length
312317
expect(dataSource.paginator!.length).toBe(1);
@@ -321,6 +326,7 @@ describe('MatTable', () => {
321326
['a_2', 'b_2', 'c_2'],
322327
['Footer A', 'Footer B', 'Footer C'],
323328
]);
329+
flush();
324330

325331
// Change filter to empty string, should match all rows
326332
dataSource.filter = '';
@@ -336,6 +342,7 @@ describe('MatTable', () => {
336342
['a_3', 'b_3', 'c_3'],
337343
['Footer A', 'Footer B', 'Footer C'],
338344
]);
345+
flush();
339346

340347
// Change filter function and filter, should match to rows with zebra.
341348
dataSource.filterPredicate = (data, filter) => {
@@ -363,6 +370,7 @@ describe('MatTable', () => {
363370
['a_2', 'b_2', 'c_2'],
364371
['Footer A', 'Footer B', 'Footer C'],
365372
]);
373+
flush();
366374

367375
// Change the filter to a falsy value that might come in from the view.
368376
dataSource.filter = 0 as any;
@@ -371,13 +379,15 @@ describe('MatTable', () => {
371379
['Column A', 'Column B', 'Column C'],
372380
['Footer A', 'Footer B', 'Footer C'],
373381
]);
382+
flush();
374383
}));
375384

376385
it('should not match concatenated words', fakeAsync(() => {
377386
// Set the value to the last character of the first
378387
// column plus the first character of the second column.
379388
dataSource.filter = '1b';
380389
fixture.detectChanges();
390+
flush();
381391
expect(dataSource.filteredData.length).toBe(0);
382392
expectTableToMatchContent(tableElement, [
383393
['Column A', 'Column B', 'Column C'],
@@ -523,6 +533,7 @@ describe('MatTable', () => {
523533
}
524534
fixture.detectChanges();
525535
flushMicrotasks(); // Resolve promise that updates paginator's length
536+
flush();
526537
expectTableToMatchContent(tableElement, [
527538
['Column A', 'Column B', 'Column C'],
528539
['a_1', 'b_1', 'c_1'],
@@ -536,6 +547,7 @@ describe('MatTable', () => {
536547
// Navigate to the next page
537548
component.paginator.nextPage();
538549
fixture.detectChanges();
550+
flush();
539551
expectTableToMatchContent(tableElement, [
540552
['Column A', 'Column B', 'Column C'],
541553
['a_6', 'b_6', 'c_6'],

tools/public_api_guard/cdk/table.md

+5-2
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,8 @@ export class CdkRowDef<T> extends BaseRowDef {
297297
// @public
298298
export class CdkTable<T> implements AfterContentChecked, CollectionViewer, OnDestroy, OnInit {
299299
constructor(_differs: IterableDiffers, _changeDetectorRef: ChangeDetectorRef, _elementRef: ElementRef, role: string, _dir: Directionality, _document: any, _platform: Platform, _viewRepeater: _ViewRepeater<T, RenderRow<T>, RowContext<T>>, _coalescedStyleScheduler: _CoalescedStyleScheduler, _viewportRuler: ViewportRuler,
300-
_stickyPositioningListener: StickyPositioningListener);
300+
_stickyPositioningListener: StickyPositioningListener,
301+
_ngZone: NgZone);
301302
addColumnDef(columnDef: CdkColumnDef): void;
302303
addFooterRowDef(footerRowDef: CdkFooterRowDef): void;
303304
addHeaderRowDef(headerRowDef: CdkHeaderRowDef): void;
@@ -344,6 +345,8 @@ export class CdkTable<T> implements AfterContentChecked, CollectionViewer, OnDes
344345
ngOnDestroy(): void;
345346
// (undocumented)
346347
ngOnInit(): void;
348+
// @deprecated (undocumented)
349+
protected readonly _ngZone: NgZone;
347350
_noDataRow: CdkNoDataRow;
348351
// (undocumented)
349352
_noDataRowOutlet: NoDataRowOutlet;
@@ -372,7 +375,7 @@ export class CdkTable<T> implements AfterContentChecked, CollectionViewer, OnDes
372375
// (undocumented)
373376
static ɵcmp: i0.ɵɵComponentDeclaration<CdkTable<any>, "cdk-table, table[cdk-table]", ["cdkTable"], { "trackBy": "trackBy"; "dataSource": "dataSource"; "multiTemplateDataRows": "multiTemplateDataRows"; "fixedLayout": "fixedLayout"; }, { "contentChanged": "contentChanged"; }, ["_noDataRow", "_contentColumnDefs", "_contentRowDefs", "_contentHeaderRowDefs", "_contentFooterRowDefs"], ["caption", "colgroup, col"]>;
374377
// (undocumented)
375-
static ɵfac: i0.ɵɵFactoryDeclaration<CdkTable<any>, [null, null, null, { attribute: "role"; }, { optional: true; }, null, null, null, null, null, { optional: true; skipSelf: true; }]>;
378+
static ɵfac: i0.ɵɵFactoryDeclaration<CdkTable<any>, [null, null, null, { attribute: "role"; }, { optional: true; }, null, null, null, null, null, { optional: true; skipSelf: true; }, { optional: true; }]>;
376379
}
377380

378381
// @public (undocumented)

0 commit comments

Comments
 (0)