Skip to content

Commit 7576a73

Browse files
crisbetotinayuangao
authored andcommitted
feat(sort): add the ability to disable sort toggling (#8643)
Adds the ability for a `mat-sort-header` or `mat-sort` instance to be disabled, preventing the user from changing the sorting direction. Fixes #8622.
1 parent 2c34a7e commit 7576a73

File tree

9 files changed

+131
-16
lines changed

9 files changed

+131
-16
lines changed

src/demo-app/table/table-demo.html

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,13 @@ <h3>MatTable With MatTableDataSource Example</h3>
221221

222222
<table-header-demo (shiftColumns)="displayedColumns.push(displayedColumns.shift())"
223223
(toggleColorColumn)="toggleColorColumn()" *ngIf="selection.isEmpty()">
224+
225+
<button mat-menu-item (click)="progressSortingDisabled = !progressSortingDisabled">
226+
<mat-icon>sort</mat-icon>
227+
Toggle Progress Sorting
228+
</button>
224229
</table-header-demo>
230+
225231
<div class="example-header example-selection-header"
226232
*ngIf="!selection.isEmpty()">
227233
{{selection.selected.length}}
@@ -257,7 +263,10 @@ <h3>MatTable With MatTableDataSource Example</h3>
257263

258264
<!-- Column Definition: Progress -->
259265
<ng-container matColumnDef="progress">
260-
<mat-header-cell *matHeaderCellDef mat-sort-header> Progress </mat-header-cell>
266+
<mat-header-cell
267+
*matHeaderCellDef
268+
[disabled]="progressSortingDisabled"
269+
mat-sort-header> Progress </mat-header-cell>
261270
<mat-cell *matCellDef="let row">
262271
<div class="demo-progress-stat">{{row.progress}}%</div>
263272
<div class="demo-progress-indicator-container">

src/demo-app/table/table-demo.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ export class TableDemo {
4343
displayedColumns: UserProperties[] = [];
4444
trackByStrategy: TrackByStrategy = 'reference';
4545
changeReferences = false;
46+
progressSortingDisabled = false;
4647
highlights = new Set<string>();
4748
wasExpanded = new Set<UserData>();
4849

src/demo-app/table/table-header-demo.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,6 @@
1515
<mat-icon>color_lens</mat-icon>
1616
Toggle Color Column
1717
</button>
18+
<ng-content></ng-content>
1819
</mat-menu>
1920
</div>

src/lib/sort/sort-header.html

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
<div class="mat-sort-header-container"
22
[class.mat-sort-header-position-before]="arrowPosition == 'before'">
33
<button class="mat-sort-header-button" type="button"
4-
[attr.aria-label]="_intl.sortButtonLabel(id)">
4+
[attr.aria-label]="_intl.sortButtonLabel(id)"
5+
[attr.disabled]="_isDisabled() || null">
56
<ng-content></ng-content>
67
</button>
78

src/lib/sort/sort-header.scss

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ $mat-sort-header-arrow-transition: 225ms cubic-bezier(0.4, 0, 0.2, 1);
88
.mat-sort-header-container {
99
display: flex;
1010
cursor: pointer;
11+
12+
.mat-sort-header-disabled & {
13+
cursor: default;
14+
}
1115
}
1216

1317
.mat-sort-header-position-before {
@@ -20,7 +24,7 @@ $mat-sort-header-arrow-transition: 225ms cubic-bezier(0.4, 0, 0.2, 1);
2024
display: flex;
2125
align-items: center;
2226
padding: 0;
23-
cursor: pointer;
27+
cursor: inherit;
2428
outline: 0;
2529
font: inherit;
2630
color: currentColor;

src/lib/sort/sort-header.ts

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,18 @@ import {MatSort, MatSortable} from './sort';
3030
import {MatSortHeaderIntl} from './sort-header-intl';
3131
import {getSortHeaderNotContainedWithinSortError} from './sort-errors';
3232
import {AnimationCurves, AnimationDurations} from '@angular/material/core';
33+
import {CanDisable, mixinDisabled} from '@angular/material/core';
34+
3335

3436
const SORT_ANIMATION_TRANSITION =
3537
AnimationDurations.ENTERING + ' ' + AnimationCurves.STANDARD_CURVE;
3638

39+
// Boilerplate for applying mixins to the sort header.
40+
/** @docs-private */
41+
export class MatSortHeaderBase {}
42+
export const _MatSortHeaderMixinBase = mixinDisabled(MatSortHeaderBase);
43+
44+
3745
/**
3846
* Applies sorting behavior (click to change sort) and styles to an element, including an
3947
* arrow to display the current sort direction.
@@ -50,12 +58,14 @@ const SORT_ANIMATION_TRANSITION =
5058
templateUrl: 'sort-header.html',
5159
styleUrls: ['sort-header.css'],
5260
host: {
53-
'(click)': '_sort.sort(this)',
61+
'(click)': '_handleClick()',
5462
'[class.mat-sort-header-sorted]': '_isSorted()',
63+
'[class.mat-sort-header-disabled]': '_isDisabled()',
5564
},
5665
encapsulation: ViewEncapsulation.None,
5766
preserveWhitespaces: false,
5867
changeDetection: ChangeDetectionStrategy.OnPush,
68+
inputs: ['disabled'],
5969
animations: [
6070
trigger('indicator', [
6171
state('asc', style({transform: 'translateY(0px)'})),
@@ -93,7 +103,7 @@ const SORT_ANIMATION_TRANSITION =
93103
])
94104
]
95105
})
96-
export class MatSortHeader implements MatSortable {
106+
export class MatSortHeader extends _MatSortHeaderMixinBase implements MatSortable, CanDisable {
97107
private _rerenderSubscription: Subscription;
98108

99109
/**
@@ -118,13 +128,15 @@ export class MatSortHeader implements MatSortable {
118128
changeDetectorRef: ChangeDetectorRef,
119129
@Optional() public _sort: MatSort,
120130
@Optional() public _cdkColumnDef: CdkColumnDef) {
131+
132+
super();
133+
121134
if (!_sort) {
122135
throw getSortHeaderNotContainedWithinSortError();
123136
}
124137

125-
this._rerenderSubscription = merge(_sort.sortChange, _intl.changes).subscribe(() => {
126-
changeDetectorRef.markForCheck();
127-
});
138+
this._rerenderSubscription = merge(_sort.sortChange, _sort._stateChanges, _intl.changes)
139+
.subscribe(() => changeDetectorRef.markForCheck());
128140
}
129141

130142
ngOnInit() {
@@ -140,9 +152,20 @@ export class MatSortHeader implements MatSortable {
140152
this._rerenderSubscription.unsubscribe();
141153
}
142154

155+
/** Handles click events on the header. */
156+
_handleClick() {
157+
if (!this._isDisabled()) {
158+
this._sort.sort(this);
159+
}
160+
}
161+
143162
/** Whether this MatSortHeader is currently sorted in either ascending or descending order. */
144163
_isSorted() {
145164
return this._sort.active == this.id &&
146165
(this._sort.direction === 'asc' || this._sort.direction === 'desc');
147166
}
167+
168+
_isDisabled() {
169+
return this._sort.disabled || this.disabled;
170+
}
148171
}

src/lib/sort/sort.md

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,19 @@ direction to sort (`asc` or `desc`).
1919
By default, a sort header starts its sorting at `asc` and then `desc`. Triggering the sort header
2020
after `desc` will remove sorting.
2121

22-
To reverse the sort order for all headers, set the `matSortStart` to `desc` on the `matSort`
23-
directive. To reverse the order only for a specific header, set the `start` input only on the header
22+
To reverse the sort order for all headers, set the `matSortStart` to `desc` on the `matSort`
23+
directive. To reverse the order only for a specific header, set the `start` input only on the header
2424
instead.
2525

26-
To prevent the user from clearing the sort sort state from an already sorted column, set
27-
`matSortDisableClear` to `true` on the `matSort` to affect all headers, or set `disableClear` to
26+
To prevent the user from clearing the sort sort state from an already sorted column, set
27+
`matSortDisableClear` to `true` on the `matSort` to affect all headers, or set `disableClear` to
2828
`true` on a specific header.
2929

30+
#### Disabling sorting
31+
32+
If you want to prevent the user from changing the sorting order of any column, you can use the
33+
`matSortDisabled` binding on the `mat-sort`, or the `disabled` on an single `mat-sort-header`.
34+
3035
#### Using sort with the mat-table
3136

3237
When used on an `mat-table` header, it is not required to set an `mat-sort-header` id on because

src/lib/sort/sort.spec.ts

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,43 @@ describe('MatSort', () => {
109109
testSingleColumnSortDirectionSequence(fixture, ['desc', 'asc', '']);
110110
});
111111

112+
it('should allow for the cycling the sort direction to be disabled per column', () => {
113+
const button = fixture.nativeElement.querySelector('#defaultSortHeaderA button');
114+
115+
component.sort('defaultSortHeaderA');
116+
expect(component.matSort.direction).toBe('asc');
117+
expect(button.getAttribute('disabled')).toBeFalsy();
118+
119+
component.disabledColumnSort = true;
120+
fixture.detectChanges();
121+
122+
component.sort('defaultSortHeaderA');
123+
expect(component.matSort.direction).toBe('asc');
124+
expect(button.getAttribute('disabled')).toBe('true');
125+
});
126+
127+
it('should allow for the cycling the sort direction to be disabled for all columns', () => {
128+
const button = fixture.nativeElement.querySelector('#defaultSortHeaderA button');
129+
130+
component.sort('defaultSortHeaderA');
131+
expect(component.matSort.active).toBe('defaultSortHeaderA');
132+
expect(component.matSort.direction).toBe('asc');
133+
expect(button.getAttribute('disabled')).toBeFalsy();
134+
135+
component.disableAllSort = true;
136+
fixture.detectChanges();
137+
138+
component.sort('defaultSortHeaderA');
139+
expect(component.matSort.active).toBe('defaultSortHeaderA');
140+
expect(component.matSort.direction).toBe('asc');
141+
expect(button.getAttribute('disabled')).toBe('true');
142+
143+
component.sort('defaultSortHeaderB');
144+
expect(component.matSort.active).toBe('defaultSortHeaderA');
145+
expect(component.matSort.direction).toBe('asc');
146+
expect(button.getAttribute('disabled')).toBe('true');
147+
});
148+
112149
it('should reset sort direction when a different column is sorted', () => {
113150
component.sort('defaultSortHeaderA');
114151
expect(component.matSort.active).toBe('defaultSortHeaderA');
@@ -211,11 +248,16 @@ function testSingleColumnSortDirectionSequence(fixture: ComponentFixture<SimpleM
211248
template: `
212249
<div matSort
213250
[matSortActive]="active"
251+
[matSortDisabled]="disableAllSort"
214252
[matSortStart]="start"
215253
[matSortDirection]="direction"
216254
[matSortDisableClear]="disableClear"
217255
(matSortChange)="latestSortEvent = $event">
218-
<div id="defaultSortHeaderA" #defaultSortHeaderA mat-sort-header="defaultSortHeaderA">
256+
<div
257+
id="defaultSortHeaderA"
258+
#defaultSortHeaderA
259+
mat-sort-header="defaultSortHeaderA"
260+
[disabled]="disabledColumnSort">
219261
A
220262
</div>
221263
<div id="defaultSortHeaderB" #defaultSortHeaderB mat-sort-header="defaultSortHeaderB">
@@ -233,6 +275,8 @@ class SimpleMatSortApp {
233275
start: SortDirection = 'asc';
234276
direction: SortDirection = '';
235277
disableClear: boolean;
278+
disabledColumnSort = false;
279+
disableAllSort = false;
236280

237281
@ViewChild(MatSort) matSort: MatSort;
238282
@ViewChild('defaultSortHeaderA') matSortHeaderDefaultA: MatSortHeader;

src/lib/sort/sort.ts

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,24 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import {Directive, EventEmitter, Input, isDevMode, Output} from '@angular/core';
9+
import {
10+
Directive,
11+
EventEmitter,
12+
Input,
13+
isDevMode,
14+
Output,
15+
OnChanges,
16+
OnDestroy,
17+
} from '@angular/core';
1018
import {coerceBooleanProperty} from '@angular/cdk/coercion';
19+
import {CanDisable, mixinDisabled} from '@angular/material/core';
1120
import {SortDirection} from './sort-direction';
1221
import {
1322
getSortInvalidDirectionError,
1423
getSortDuplicateSortableIdError,
1524
getSortHeaderMissingIdError
1625
} from './sort-errors';
26+
import {Subject} from 'rxjs/Subject';
1727

1828
/** Interface for a directive that holds sorting state consumed by `MatSortHeader`. */
1929
export interface MatSortable {
@@ -36,15 +46,24 @@ export interface Sort {
3646
direction: SortDirection;
3747
}
3848

49+
// Boilerplate for applying mixins to MatSort.
50+
/** @docs-private */
51+
export class MatSortBase {}
52+
export const _MatSortMixinBase = mixinDisabled(MatSortBase);
53+
3954
/** Container for MatSortables to manage the sort state and provide default sort parameters. */
4055
@Directive({
4156
selector: '[matSort]',
42-
exportAs: 'matSort'
57+
exportAs: 'matSort',
58+
inputs: ['disabled: matSortDisabled']
4359
})
44-
export class MatSort {
60+
export class MatSort extends _MatSortMixinBase implements CanDisable, OnChanges, OnDestroy {
4561
/** Collection of all registered sortables that this directive manages. */
4662
sortables = new Map<string, MatSortable>();
4763

64+
/** Used to notify any child components listening to state changes. */
65+
_stateChanges = new Subject<void>();
66+
4867
/** The id of the most recently sorted MatSortable. */
4968
@Input('matSortActive') active: string;
5069

@@ -125,6 +144,14 @@ export class MatSort {
125144
if (nextDirectionIndex >= sortDirectionCycle.length) { nextDirectionIndex = 0; }
126145
return sortDirectionCycle[nextDirectionIndex];
127146
}
147+
148+
ngOnChanges() {
149+
this._stateChanges.next();
150+
}
151+
152+
ngOnDestroy() {
153+
this._stateChanges.complete();
154+
}
128155
}
129156

130157
/** Returns the sort direction cycle to use given the provided parameters of order and clear. */

0 commit comments

Comments
 (0)