Skip to content

Commit edbbc1b

Browse files
authored
feat(table): support sticky headers, footers, and columns (#11483)
* feat(table): support sticky headers, footers, and columns * review * support rtl sticky columns * move sticky to mixin * add bidi to bazel BUILD for spec * fix prerender; reverse rtl * minor revisions * minor changes for g3 internal tests
1 parent 966910a commit edbbc1b

34 files changed

+1745
-56
lines changed

.firebaserc

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"projects": {
3+
"staging": "material2-dev"
4+
}
5+
}

src/cdk/table/BUILD.bazel

+2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ ng_module(
88
srcs = glob(["**/*.ts"], exclude=["**/*.spec.ts"]),
99
module_name = "@angular/cdk/table",
1010
deps = [
11+
"//src/cdk/bidi",
1112
"//src/cdk/collections",
1213
"//src/cdk/coercion",
1314
"@rxjs",
@@ -21,6 +22,7 @@ ts_library(
2122
srcs = glob(["**/*.spec.ts"]),
2223
deps = [
2324
":table",
25+
"//src/cdk/bidi",
2426
"//src/cdk/collections",
2527
"@rxjs",
2628
"@rxjs//operators"

src/cdk/table/can-stick.ts

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
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+
import {coerceBooleanProperty} from '@angular/cdk/coercion';
10+
11+
/** @docs-private */
12+
export type Constructor<T> = new(...args: any[]) => T;
13+
14+
/**
15+
* Interface for a mixin to provide a directive with a function that checks if the sticky input has
16+
* been changed since the last time the function was called. Essentially adds a dirty-check to the
17+
* sticky value.
18+
* @docs-private
19+
*/
20+
export interface CanStick {
21+
/** Whether sticky positioning should be applied. */
22+
sticky: boolean;
23+
24+
/** Whether the sticky input has changed since it was last checked. */
25+
_hasStickyChanged: boolean;
26+
27+
/** Whether the sticky value has changed since this was last called. */
28+
hasStickyChanged(): boolean;
29+
30+
/** Resets the dirty check for cases where the sticky state has been used without checking. */
31+
resetStickyChanged(): void;
32+
}
33+
34+
/**
35+
* Mixin to provide a directive with a function that checks if the sticky input has been
36+
* changed since the last time the function was called. Essentially adds a dirty-check to the
37+
* sticky value.
38+
*/
39+
export function mixinHasStickyInput<T extends Constructor<{}>>(base: T):
40+
Constructor<CanStick> & T {
41+
return class extends base {
42+
/** Whether sticky positioning should be applied. */
43+
get sticky(): boolean { return this._sticky; }
44+
set sticky(v: boolean) {
45+
const prevValue = this._sticky;
46+
this._sticky = coerceBooleanProperty(v);
47+
this._hasStickyChanged = prevValue !== this._sticky;
48+
}
49+
_sticky: boolean = false;
50+
51+
/** Whether the sticky input has changed since it was last checked. */
52+
_hasStickyChanged: boolean = false;
53+
54+
/** Whether the sticky value has changed since this was last called. */
55+
hasStickyChanged(): boolean {
56+
const hasStickyChanged = this._hasStickyChanged;
57+
this._hasStickyChanged = false;
58+
return hasStickyChanged;
59+
}
60+
61+
/** Resets the dirty check for cases where the sticky state has been used without checking. */
62+
resetStickyChanged() {
63+
this._hasStickyChanged = false;
64+
}
65+
66+
constructor(...args: any[]) { super(...args); }
67+
};
68+
}

src/cdk/table/cell.ts

+26-2
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 {CanStick, mixinHasStickyInput} from './can-stick';
1012

1113
/** Base interface for a cell definition. Captures a column's cell template definition. */
1214
export interface CellDef {
@@ -40,12 +42,20 @@ 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
*/
47-
@Directive({selector: '[cdkColumnDef]'})
48-
export class CdkColumnDef {
54+
@Directive({
55+
selector: '[cdkColumnDef]',
56+
inputs: ['sticky']
57+
})
58+
export class CdkColumnDef extends _CdkColumnDefBase implements CanStick {
4959
/** Unique name for this column. */
5060
@Input('cdkColumnDef')
5161
get name(): string { return this._name; }
@@ -59,6 +69,20 @@ export class CdkColumnDef {
5969
}
6070
_name: string;
6171

72+
/**
73+
* Whether this column should be sticky positioned on the end of the row. Should make sure
74+
* that it mimics the `CanStick` mixin such that `_hasStickyChanged` is set to true if the value
75+
* has been changed.
76+
*/
77+
@Input('stickyEnd')
78+
get stickyEnd(): boolean { return this._stickyEnd; }
79+
set stickyEnd(v: boolean) {
80+
const prevValue = this._stickyEnd;
81+
this._stickyEnd = coerceBooleanProperty(v);
82+
this._hasStickyChanged = prevValue !== this._stickyEnd;
83+
}
84+
_stickyEnd: boolean = false;
85+
6286
/** @docs-private */
6387
@ContentChild(CdkCellDef) cell: CdkCellDef;
6488

src/cdk/table/public-api.ts

+2
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 './can-stick';
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

+33-17
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
ViewEncapsulation,
2121
} from '@angular/core';
2222
import {CdkCellDef, CdkColumnDef} from './cell';
23+
import {CanStick, mixinHasStickyInput} from './can-stick';
2324

2425
/**
2526
* The row template that can be used by the mat-table. Should not be used outside of the
@@ -44,8 +45,8 @@ export abstract class BaseRowDef implements OnChanges {
4445
ngOnChanges(changes: SimpleChanges): void {
4546
// Create a new columns differ if one does not yet exist. Initialize it based on initial value
4647
// of the columns property or an empty array if none is provided.
47-
const columns = changes['columns'].currentValue || [];
4848
if (!this._columnsDiffer) {
49+
const columns = (changes['columns'] && changes['columns'].currentValue) || [];
4950
this._columnsDiffer = this._differs.find(columns).create();
5051
this._columnsDiffer.diff(columns);
5152
}
@@ -60,44 +61,64 @@ export abstract class BaseRowDef implements OnChanges {
6061
}
6162

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

75+
// Boilerplate for applying mixins to CdkHeaderRowDef.
76+
/** @docs-private */
77+
export class CdkHeaderRowDefBase extends BaseRowDef {}
78+
export const _CdkHeaderRowDefBase = mixinHasStickyInput(CdkHeaderRowDefBase);
79+
6680
/**
6781
* Header row definition for the CDK table.
6882
* Captures the header row's template and other header properties such as the columns to display.
6983
*/
7084
@Directive({
7185
selector: '[cdkHeaderRowDef]',
72-
inputs: ['columns: cdkHeaderRowDef'],
86+
inputs: ['columns: cdkHeaderRowDef', 'sticky: cdkHeaderRowDefSticky'],
7387
})
74-
export class CdkHeaderRowDef extends BaseRowDef {
88+
export class CdkHeaderRowDef extends _CdkHeaderRowDefBase implements CanStick, OnChanges {
7589
constructor(template: TemplateRef<any>, _differs: IterableDiffers) {
7690
super(template, _differs);
7791
}
7892

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;
93+
// Prerender fails to recognize that ngOnChanges in a part of this class through inheritance.
94+
// Explicitly define it so that the method is called as part of the Angular lifecycle.
95+
ngOnChanges(changes: SimpleChanges): void {
96+
super.ngOnChanges(changes);
8297
}
8398
}
8499

100+
// Boilerplate for applying mixins to CdkFooterRowDef.
101+
/** @docs-private */
102+
export class CdkFooterRowDefBase extends BaseRowDef {}
103+
export const _CdkFooterRowDefBase = mixinHasStickyInput(CdkFooterRowDefBase);
104+
85105
/**
86106
* Footer row definition for the CDK table.
87107
* Captures the footer row's template and other footer properties such as the columns to display.
88108
*/
89109
@Directive({
90110
selector: '[cdkFooterRowDef]',
91-
inputs: ['columns: cdkFooterRowDef'],
111+
inputs: ['columns: cdkFooterRowDef', 'sticky: cdkFooterRowDefSticky'],
92112
})
93-
export class CdkFooterRowDef extends BaseRowDef {
113+
export class CdkFooterRowDef extends _CdkFooterRowDefBase implements CanStick, OnChanges {
94114
constructor(template: TemplateRef<any>, _differs: IterableDiffers) {
95115
super(template, _differs);
96116
}
97117

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;
118+
// Prerender fails to recognize that ngOnChanges in a part of this class through inheritance.
119+
// Explicitly define it so that the method is called as part of the Angular lifecycle.
120+
ngOnChanges(changes: SimpleChanges): void {
121+
super.ngOnChanges(changes);
101122
}
102123
}
103124

@@ -124,11 +145,6 @@ export class CdkRowDef<T> extends BaseRowDef {
124145
constructor(template: TemplateRef<any>, _differs: IterableDiffers) {
125146
super(template, _differs);
126147
}
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-
}
132148
}
133149

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

0 commit comments

Comments
 (0)