Skip to content

Commit b1cc050

Browse files
authored
fix(cdk/table): error if data is accessed too early (#30817)
Fixes that the table was throwing an error if `renderRows` is called before the data has been loaded for the first time. Fixes #30795.
1 parent 0d1c848 commit b1cc050

File tree

4 files changed

+21
-6
lines changed

4 files changed

+21
-6
lines changed

goldens/cdk/table/index.api.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -313,7 +313,7 @@ export class CdkTable<T> implements AfterContentInit, AfterContentChecked, Colle
313313
_contentFooterRowDefs: QueryList<CdkFooterRowDef>;
314314
_contentHeaderRowDefs: QueryList<CdkHeaderRowDef>;
315315
_contentRowDefs: QueryList<CdkRowDef<T>>;
316-
protected _data: readonly T[];
316+
protected _data: readonly T[] | undefined;
317317
get dataSource(): CdkTableDataSourceInput<T>;
318318
set dataSource(dataSource: CdkTableDataSourceInput<T>);
319319
// (undocumented)

src/cdk/table/BUILD.bazel

+1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ ts_project(
4343
deps = [
4444
":table",
4545
"//:node_modules/@angular/core",
46+
"//:node_modules/@angular/platform-browser",
4647
"//:node_modules/rxjs",
4748
"//src/cdk/bidi",
4849
"//src/cdk/collections",

src/cdk/table/table.spec.ts

+10
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
ViewChild,
1515
inject,
1616
} from '@angular/core';
17+
import {By} from '@angular/platform-browser';
1718
import {ComponentFixture, TestBed, fakeAsync, flush, waitForAsync} from '@angular/core/testing';
1819
import {BehaviorSubject, Observable, combineLatest, of as observableOf} from 'rxjs';
1920
import {map} from 'rxjs/operators';
@@ -376,6 +377,15 @@ describe('CdkTable', () => {
376377
expect(colgroupsAndCols.map(e => e.nodeName.toLowerCase())).toEqual(['colgroup', 'col', 'col']);
377378
}));
378379

380+
it('should not throw if `renderRows` is called too early', () => {
381+
// Note that we don't call `detectChanges` here, because we're testing specifically
382+
// what happens when `renderRows` is called before the first change detection run.
383+
const fixture = createComponent(SimpleCdkTableApp);
384+
const table = fixture.debugElement.query(By.directive(CdkTable))
385+
.componentInstance as CdkTable<unknown>;
386+
expect(() => table.renderRows()).not.toThrow();
387+
});
388+
379389
describe('with different data inputs other than data source', () => {
380390
let baseData: TestData[] = [
381391
{a: 'a_1', b: 'b_1', c: 'c_1'},

src/cdk/table/table.ts

+9-5
Original file line numberDiff line numberDiff line change
@@ -307,7 +307,7 @@ export class CdkTable<T>
307307
private _document = inject(DOCUMENT);
308308

309309
/** Latest data provided by the data source. */
310-
protected _data: readonly T[];
310+
protected _data: readonly T[] | undefined;
311311

312312
/** Subject that emits when the component has been destroyed. */
313313
private readonly _onDestroy = new Subject<void>();
@@ -621,17 +621,17 @@ export class CdkTable<T>
621621

622622
this._isServer = !this._platform.isBrowser;
623623
this._isNativeHtmlTable = this._elementRef.nativeElement.nodeName === 'TABLE';
624-
}
625-
626-
ngOnInit() {
627-
this._setupStickyStyler();
628624

629625
// Set up the trackBy function so that it uses the `RenderRow` as its identity by default. If
630626
// the user has provided a custom trackBy, return the result of that function as evaluated
631627
// with the values of the `RenderRow`'s data and index.
632628
this._dataDiffer = this._differs.find([]).create((_i: number, dataRow: RenderRow<T>) => {
633629
return this.trackBy ? this.trackBy(dataRow.dataIndex, dataRow.data) : dataRow;
634630
});
631+
}
632+
633+
ngOnInit() {
634+
this._setupStickyStyler();
635635

636636
this._viewportRuler
637637
.change()
@@ -981,6 +981,10 @@ export class CdkTable<T>
981981
const prevCachedRenderRows = this._cachedRenderRowsMap;
982982
this._cachedRenderRowsMap = new Map();
983983

984+
if (!this._data) {
985+
return renderRows;
986+
}
987+
984988
// For each data object, get the list of rows that should be rendered, represented by the
985989
// respective `RenderRow` object which is the pair of `data` and `CdkRowDef`.
986990
for (let i = 0; i < this._data.length; i++) {

0 commit comments

Comments
 (0)