Skip to content

Commit 1117b1c

Browse files
committed
feat(table): support sticky headers, footers, and columns
1 parent 984f8dc commit 1117b1c

29 files changed

+1622
-39
lines changed

src/cdk/table/cell.ts

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
*/
88

99
import {ContentChild, Directive, ElementRef, Input, TemplateRef} from '@angular/core';
10+
import {coerceBooleanProperty} from '@angular/cdk/coercion';
11+
import {HasStickyState, mixinHasStickyInput} from './has-sticky-state';
1012

1113
/** Base interface for a cell definition. Captures a column's cell template definition. */
1214
export interface CellDef {
@@ -40,12 +42,17 @@ export class CdkFooterCellDef implements CellDef {
4042
constructor(/** @docs-private */ public template: TemplateRef<any>) { }
4143
}
4244

45+
// Boilerplate for applying mixins to CdkColumnDef.
46+
/** @docs-private */
47+
export class CdkColumnDefBase {}
48+
export const _CdkColumnDefBase = mixinHasStickyInput(CdkColumnDefBase);
49+
4350
/**
4451
* Column definition for the CDK table.
4552
* Defines a set of cells available for a table column.
4653
*/
4754
@Directive({selector: '[cdkColumnDef]'})
48-
export class CdkColumnDef {
55+
export class CdkColumnDef extends _CdkColumnDefBase implements HasStickyState {
4956
/** Unique name for this column. */
5057
@Input('cdkColumnDef')
5158
get name(): string { return this._name; }
@@ -59,6 +66,26 @@ export class CdkColumnDef {
5966
}
6067
_name: string;
6168

69+
/** Whether this column should be sticky positioned on the left of the row */
70+
@Input('stickyLeft')
71+
get stickyLeft(): boolean { return this._stickyLeft; }
72+
set stickyLeft(v: boolean) {
73+
const prevValue = this._stickyLeft;
74+
this._stickyLeft = coerceBooleanProperty(v);
75+
this._hasStickyChanged = prevValue !== this._stickyLeft;
76+
}
77+
_stickyLeft: boolean = false;
78+
79+
/** Whether this column should be sticky positioned on the right of the right */
80+
@Input('stickyRight')
81+
get stickyRight(): boolean { return this._stickyRight; }
82+
set stickyRight(v: boolean) {
83+
const prevValue = this._stickyRight;
84+
this._stickyRight = coerceBooleanProperty(v);
85+
this._hasStickyChanged = prevValue !== this._stickyRight;
86+
}
87+
_stickyRight: boolean = false;
88+
6289
/** @docs-private */
6390
@ContentChild(CdkCellDef) cell: CdkCellDef;
6491

src/cdk/table/has-sticky-state.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
/** @docs-private */
10+
export type Constructor<T> = new(...args: any[]) => T;
11+
12+
/**
13+
* Interface for a mixin to provide a directive with a function that checks if the sticky input has
14+
* been changed since the last time the function was called. Essentially adds a dirty-check to the
15+
* sticky value.
16+
* @docs-private
17+
*/
18+
export interface HasStickyState {
19+
/** State of whether the sticky input has changed since it was last checked. */
20+
_hasStickyChanged: boolean;
21+
22+
checkStickyChanged(): boolean;
23+
24+
resetStickyChanged(): void;
25+
}
26+
27+
/**
28+
* Mixin to provide a directive with a function that checks if the sticky input has been
29+
* changed since the last time the function was called. Essentially adds a dirty-check to the
30+
* sticky value.
31+
*/
32+
export function mixinHasStickyInput<T extends Constructor<{}>>(base: T):
33+
Constructor<HasStickyState> & T {
34+
return class extends base {
35+
/** State of whether the sticky input has changed since it was last checked. */
36+
_hasStickyChanged: boolean = false;
37+
38+
/** Whether the sticky value has changed since this was last called. */
39+
checkStickyChanged(): boolean {
40+
const hasStickyChanged = this._hasStickyChanged;
41+
this._hasStickyChanged = false;
42+
return hasStickyChanged;
43+
}
44+
45+
/** Resets the dirty check for cases where the sticky state has been used without checking. */
46+
resetStickyChanged() {
47+
this._hasStickyChanged = false;
48+
}
49+
50+
constructor(...args: any[]) { super(...args); }
51+
};
52+
}

src/cdk/table/public-api.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ export * from './table';
1010
export * from './cell';
1111
export * from './row';
1212
export * from './table-module';
13+
export * from './sticky-styler';
14+
export * from './has-sticky-state';
1315

1416
/** Re-export DataSource for a more intuitive experience for users of just the table. */
1517
export {DataSource} from '@angular/cdk/collections';

src/cdk/table/row.ts

Lines changed: 42 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ import {
2020
ViewEncapsulation,
2121
} from '@angular/core';
2222
import {CdkCellDef, CdkColumnDef} from './cell';
23+
import {coerceBooleanProperty} from '@angular/cdk/coercion';
24+
import {HasStickyState, mixinHasStickyInput} from './has-sticky-state';
2325

2426
/**
2527
* The row template that can be used by the mat-table. Should not be used outside of the
@@ -44,8 +46,8 @@ export abstract class BaseRowDef implements OnChanges {
4446
ngOnChanges(changes: SimpleChanges): void {
4547
// Create a new columns differ if one does not yet exist. Initialize it based on initial value
4648
// of the columns property or an empty array if none is provided.
47-
const columns = changes['columns'].currentValue || [];
4849
if (!this._columnsDiffer) {
50+
const columns = (changes['columns'] && changes['columns'].currentValue) || [];
4951
this._columnsDiffer = this._differs.find(columns).create();
5052
this._columnsDiffer.diff(columns);
5153
}
@@ -60,44 +62,68 @@ export abstract class BaseRowDef implements OnChanges {
6062
}
6163

6264
/** Gets this row def's relevant cell template from the provided column def. */
63-
abstract extractCellTemplate(column: CdkColumnDef): TemplateRef<any>;
65+
extractCellTemplate(column: CdkColumnDef): TemplateRef<any> {
66+
if (this instanceof CdkHeaderRowDef) {
67+
return column.headerCell.template;
68+
} if (this instanceof CdkFooterRowDef) {
69+
return column.footerCell.template;
70+
} else {
71+
return column.cell.template;
72+
}
73+
}
6474
}
6575

76+
// Boilerplate for applying mixins to CdkHeaderRowDef.
77+
/** @docs-private */
78+
export class CdkHeaderRowDefBase extends BaseRowDef {}
79+
export const _CdkHeaderRowDefBase = mixinHasStickyInput(CdkHeaderRowDefBase);
80+
6681
/**
6782
* Header row definition for the CDK table.
6883
* Captures the header row's template and other header properties such as the columns to display.
6984
*/
7085
@Directive({
7186
selector: '[cdkHeaderRowDef]',
72-
inputs: ['columns: cdkHeaderRowDef'],
87+
inputs: ['columns: cdkHeaderRowDef', 'sticky: cdkHeaderRowDefSticky'],
7388
})
74-
export class CdkHeaderRowDef extends BaseRowDef {
75-
constructor(template: TemplateRef<any>, _differs: IterableDiffers) {
76-
super(template, _differs);
89+
export class CdkHeaderRowDef extends _CdkHeaderRowDefBase implements HasStickyState {
90+
set sticky(v: boolean) {
91+
const prevValue = this._sticky;
92+
this._sticky = coerceBooleanProperty(v);
93+
this._hasStickyChanged = prevValue !== this._sticky;
7794
}
95+
get sticky(): boolean { return this._sticky; }
96+
_sticky: boolean;
7897

79-
/** Gets this row def's relevant cell template from the provided column def. */
80-
extractCellTemplate(column: CdkColumnDef): TemplateRef<any> {
81-
return column.headerCell.template;
98+
constructor(template: TemplateRef<any>, _differs: IterableDiffers) {
99+
super(template, _differs);
82100
}
83101
}
84102

103+
// Boilerplate for applying mixins to CdkFooterRowDef.
104+
/** @docs-private */
105+
export class CdkFooterRowDefBase extends BaseRowDef {}
106+
export const _CdkFooterRowDefBase = mixinHasStickyInput(CdkFooterRowDefBase);
107+
85108
/**
86109
* Footer row definition for the CDK table.
87110
* Captures the footer row's template and other footer properties such as the columns to display.
88111
*/
89112
@Directive({
90113
selector: '[cdkFooterRowDef]',
91-
inputs: ['columns: cdkFooterRowDef'],
114+
inputs: ['columns: cdkFooterRowDef', 'sticky: cdkFooterRowDefSticky'],
92115
})
93-
export class CdkFooterRowDef extends BaseRowDef {
94-
constructor(template: TemplateRef<any>, _differs: IterableDiffers) {
95-
super(template, _differs);
116+
export class CdkFooterRowDef extends _CdkFooterRowDefBase implements HasStickyState {
117+
set sticky(v: boolean) {
118+
const prevValue = this._sticky;
119+
this._sticky = coerceBooleanProperty(v);
120+
this._hasStickyChanged = prevValue !== this._sticky;
96121
}
122+
get sticky(): boolean { return this._sticky; }
123+
_sticky: boolean;
97124

98-
/** Gets this row def's relevant cell template from the provided column def. */
99-
extractCellTemplate(column: CdkColumnDef): TemplateRef<any> {
100-
return column.footerCell.template;
125+
constructor(template: TemplateRef<any>, _differs: IterableDiffers) {
126+
super(template, _differs);
101127
}
102128
}
103129

@@ -124,11 +150,6 @@ export class CdkRowDef<T> extends BaseRowDef {
124150
constructor(template: TemplateRef<any>, _differs: IterableDiffers) {
125151
super(template, _differs);
126152
}
127-
128-
/** Gets this row def's relevant cell template from the provided column def. */
129-
extractCellTemplate(column: CdkColumnDef): TemplateRef<any> {
130-
return column.cell.template;
131-
}
132153
}
133154

134155
/** Context provided to the row cells when `multiTemplateDataRows` is false */

0 commit comments

Comments
 (0)