Skip to content

Commit 99f39ca

Browse files
andrewseguinmmalerba
authored andcommitted
fix(table): data source can listen for init from sort, page (#10593)
* fix(table): data source can listen for init from sort, page * fix after rebase:
1 parent 54301da commit 99f39ca

File tree

11 files changed

+205
-49
lines changed

11 files changed

+205
-49
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export class CustomTableDemo {
2525
@ViewChild('simpleTableSort') simpleTableSort: MatSort;
2626
@ViewChild('wrapperTableSort') wrapperTableSort: MatSort;
2727

28-
ngAfterViewInit() {
28+
ngOnInit() {
2929
this.simpleTableDataSource.sort = this.simpleTableSort;
3030
this.wrapperTableDataSource.sort = this.wrapperTableSort;
3131
}

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

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -81,14 +81,10 @@ export class TableDemo {
8181
(data: UserData, filter: string) => data.name.indexOf(filter) != -1;
8282
}
8383

84-
ngAfterViewInit() {
85-
// Needs to be set up after the view is initialized since the data source will look at the sort
86-
// and paginator's initial values to know what data should be rendered.
87-
this.matTableDataSource!.paginator = this.paginatorForDataSource;
84+
ngOnInit() {
8885
this.matTableDataSource!.sort = this.sortForDataSource;
89-
}
86+
this.matTableDataSource!.paginator = this.paginatorForDataSource;
9087

91-
ngOnInit() {
9288
this.connect();
9389
fromEvent(this.filter.nativeElement, 'keyup')
9490
.pipe(

src/lib/paginator/paginator.spec.ts

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {async, ComponentFixture, TestBed, inject} from '@angular/core/testing';
1+
import {async, ComponentFixture, TestBed, inject, tick, fakeAsync} from '@angular/core/testing';
22
import {MatPaginatorModule} from './index';
33
import {MatPaginator, PageEvent} from './paginator';
44
import {Component, ViewChild} from '@angular/core';
@@ -124,27 +124,33 @@ describe('MatPaginator', () => {
124124
expect(paginator.pageIndex).toBe(0);
125125
expect(component.latestPageEvent ? component.latestPageEvent.pageIndex : null).toBe(0);
126126
});
127-
128127
});
129128

130129
it('should be able to show the first/last buttons', () => {
131130
expect(getFirstButton(fixture))
132-
.toBeNull('Expected first button to not exist.');
131+
.toBeNull('Expected first button to not exist.');
133132

134133
expect(getLastButton(fixture))
135-
.toBeNull('Expected last button to not exist.');
134+
.toBeNull('Expected last button to not exist.');
136135

137136
fixture.componentInstance.showFirstLastButtons = true;
138137
fixture.detectChanges();
139138

140139
expect(getFirstButton(fixture))
141-
.toBeTruthy('Expected first button to be rendered.');
140+
.toBeTruthy('Expected first button to be rendered.');
142141

143142
expect(getLastButton(fixture))
144-
.toBeTruthy('Expected last button to be rendered.');
145-
143+
.toBeTruthy('Expected last button to be rendered.');
146144
});
147145

146+
it('should mark itself as initialized', fakeAsync(() => {
147+
let isMarkedInitialized = false;
148+
paginator.initialized.subscribe(() => isMarkedInitialized = true);
149+
150+
tick();
151+
expect(isMarkedInitialized).toBeTruthy();
152+
}));
153+
148154
describe('when showing the first and last button', () => {
149155

150156
beforeEach(() => {

src/lib/paginator/paginator.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
} from '@angular/core';
2121
import {Subscription} from 'rxjs';
2222
import {MatPaginatorIntl} from './paginator-intl';
23+
import {HasInitialized, mixinInitialized} from '@angular/material/core';
2324

2425
/** The default page size if there is no page size and there are no provided page size options. */
2526
const DEFAULT_PAGE_SIZE = 50;
@@ -39,6 +40,11 @@ export class PageEvent {
3940
length: number;
4041
}
4142

43+
// Boilerplate for applying mixins to MatPaginator.
44+
/** @docs-private */
45+
export class MatPaginatorBase {}
46+
export const _MatPaginatorBase = mixinInitialized(MatPaginatorBase);
47+
4248
/**
4349
* Component to provide navigation between paged information. Displays the size of the current
4450
* page, user-selectable options to change that size, what items are being shown, and
@@ -56,7 +62,7 @@ export class PageEvent {
5662
changeDetection: ChangeDetectionStrategy.OnPush,
5763
encapsulation: ViewEncapsulation.None,
5864
})
59-
export class MatPaginator implements OnInit, OnDestroy {
65+
export class MatPaginator extends _MatPaginatorBase implements OnInit, OnDestroy, HasInitialized {
6066
private _initialized: boolean;
6167
private _intlChanges: Subscription;
6268

@@ -121,12 +127,14 @@ export class MatPaginator implements OnInit, OnDestroy {
121127

122128
constructor(public _intl: MatPaginatorIntl,
123129
private _changeDetectorRef: ChangeDetectorRef) {
130+
super();
124131
this._intlChanges = _intl.changes.subscribe(() => this._changeDetectorRef.markForCheck());
125132
}
126133

127134
ngOnInit() {
128135
this._initialized = true;
129136
this._updateDisplayedPageSizeOptions();
137+
this._markInitialized();
130138
}
131139

132140
ngOnDestroy() {

src/lib/sort/sort.spec.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
wrappedErrorMessage
88
} from '@angular/cdk/testing';
99
import {Component, ElementRef, ViewChild} from '@angular/core';
10-
import {async, ComponentFixture, inject, TestBed} from '@angular/core/testing';
10+
import {async, ComponentFixture, fakeAsync, inject, TestBed, tick} from '@angular/core/testing';
1111
import {By} from '@angular/platform-browser';
1212
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
1313
import {Observable} from 'rxjs';
@@ -65,6 +65,14 @@ describe('MatSort', () => {
6565
expect(sortables.size).toBe(0);
6666
});
6767

68+
it('should mark itself as initialized', fakeAsync(() => {
69+
let isMarkedInitialized = false;
70+
component.matSort.initialized.subscribe(() => isMarkedInitialized = true);
71+
72+
tick();
73+
expect(isMarkedInitialized).toBeTruthy();
74+
}));
75+
6876
it('should use the column definition if used within a cdk table', () => {
6977
let cdkTableMatSortAppFixture = TestBed.createComponent(CdkTableMatSortApp);
7078
let cdkTableMatSortAppComponent = cdkTableMatSortAppFixture.componentInstance;

src/lib/sort/sort.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,18 @@ import {
1111
EventEmitter,
1212
Input,
1313
isDevMode,
14-
Output,
1514
OnChanges,
1615
OnDestroy,
16+
OnInit,
17+
Output,
1718
} from '@angular/core';
1819
import {coerceBooleanProperty} from '@angular/cdk/coercion';
19-
import {CanDisable, mixinDisabled} from '@angular/material/core';
20+
import {CanDisable, HasInitialized, mixinDisabled, mixinInitialized} from '@angular/material/core';
2021
import {SortDirection} from './sort-direction';
2122
import {
22-
getSortInvalidDirectionError,
2323
getSortDuplicateSortableIdError,
24-
getSortHeaderMissingIdError
24+
getSortHeaderMissingIdError,
25+
getSortInvalidDirectionError
2526
} from './sort-errors';
2627
import {Subject} from 'rxjs';
2728

@@ -49,15 +50,16 @@ export interface Sort {
4950
// Boilerplate for applying mixins to MatSort.
5051
/** @docs-private */
5152
export class MatSortBase {}
52-
export const _MatSortMixinBase = mixinDisabled(MatSortBase);
53+
export const _MatSortMixinBase = mixinInitialized(mixinDisabled(MatSortBase));
5354

5455
/** Container for MatSortables to manage the sort state and provide default sort parameters. */
5556
@Directive({
5657
selector: '[matSort]',
5758
exportAs: 'matSort',
5859
inputs: ['disabled: matSortDisabled']
5960
})
60-
export class MatSort extends _MatSortMixinBase implements CanDisable, OnChanges, OnDestroy {
61+
export class MatSort extends _MatSortMixinBase
62+
implements CanDisable, HasInitialized, OnChanges, OnDestroy, OnInit {
6163
/** Collection of all registered sortables that this directive manages. */
6264
sortables = new Map<string, MatSortable>();
6365

@@ -145,6 +147,10 @@ export class MatSort extends _MatSortMixinBase implements CanDisable, OnChanges,
145147
return sortDirectionCycle[nextDirectionIndex];
146148
}
147149

150+
ngOnInit() {
151+
this._markInitialized();
152+
}
153+
148154
ngOnChanges() {
149155
this._stateChanges.next();
150156
}

src/lib/table/table-data-source.ts

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,17 @@
88

99
import {_isNumberValue} from '@angular/cdk/coercion';
1010
import {DataSource} from '@angular/cdk/table';
11+
import {
12+
BehaviorSubject,
13+
combineLatest,
14+
merge,
15+
Observable,
16+
of as observableOf,
17+
Subscription
18+
} from 'rxjs';
1119
import {MatPaginator, PageEvent} from '@angular/material/paginator';
1220
import {MatSort, Sort} from '@angular/material/sort';
13-
import {BehaviorSubject, combineLatest, EMPTY, Observable, Subscription} from 'rxjs';
14-
import {map, startWith} from 'rxjs/operators';
21+
import {map} from 'rxjs/operators';
1522

1623

1724
/**
@@ -174,9 +181,17 @@ export class MatTableDataSource<T> extends DataSource<T> {
174181
*/
175182
_updateChangeSubscription() {
176183
// Sorting and/or pagination should be watched if MatSort and/or MatPaginator are provided.
177-
// Otherwise, use an empty observable stream to take their place.
178-
const sortChange: Observable<Sort> = this._sort ? this._sort.sortChange : EMPTY;
179-
const pageChange: Observable<PageEvent> = this._paginator ? this._paginator.page : EMPTY;
184+
// The events should emit whenever the component emits a change or initializes, or if no
185+
// component is provided, a stream with just a null event should be provided.
186+
// The `sortChange` and `pageChange` acts as a signal to the combineLatests below so that the
187+
// pipeline can progress to the next step. Note that the value from these streams are not used,
188+
// they purely act as a signal to progress in the pipeline.
189+
const sortChange: Observable<Sort|null> = this._sort ?
190+
merge<Sort>(this._sort.sortChange, this._sort.initialized) :
191+
observableOf(null);
192+
const pageChange: Observable<PageEvent|null> = this._paginator ?
193+
merge<PageEvent>(this._paginator.page, this._paginator.initialized) :
194+
observableOf(null);
180195

181196
if (this._renderChangesSubscription) {
182197
this._renderChangesSubscription.unsubscribe();
@@ -187,10 +202,10 @@ export class MatTableDataSource<T> extends DataSource<T> {
187202
const filteredData = combineLatest(dataStream, this._filter)
188203
.pipe(map(([data]) => this._filterData(data)));
189204
// Watch for filtered data or sort changes to provide an ordered set of data.
190-
const orderedData = combineLatest(filteredData, sortChange.pipe(startWith(null!)))
205+
const orderedData = combineLatest(filteredData, sortChange)
191206
.pipe(map(([data]) => this._orderData(data)));
192207
// Watch for ordered data or page changes to provide a paged set of data.
193-
const paginatedData = combineLatest(orderedData, pageChange.pipe(startWith(null!)))
208+
const paginatedData = combineLatest(orderedData, pageChange)
194209
.pipe(map(([data]) => this._pageData(data)));
195210
// Watched for paged data changes and send the result to the table to render.
196211
paginatedData.subscribe(data => this._renderData.next(data));

0 commit comments

Comments
 (0)