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:
This commit is contained in:
Andrew Seguin
2018-04-19 10:43:41 -07:00
committed by mmalerba
parent 54301da829
commit 99f39ca479
11 changed files with 206 additions and 50 deletions
@@ -25,7 +25,7 @@ export class CustomTableDemo {
@ViewChild('simpleTableSort') simpleTableSort: MatSort;
@ViewChild('wrapperTableSort') wrapperTableSort: MatSort;
ngAfterViewInit() {
ngOnInit() {
this.simpleTableDataSource.sort = this.simpleTableSort;
this.wrapperTableDataSource.sort = this.wrapperTableSort;
}
+3 -7
View File
@@ -81,14 +81,10 @@ export class TableDemo {
(data: UserData, filter: string) => data.name.indexOf(filter) != -1;
}
ngAfterViewInit() {
// Needs to be set up after the view is initialized since the data source will look at the sort
// and paginator's initial values to know what data should be rendered.
this.matTableDataSource!.paginator = this.paginatorForDataSource;
this.matTableDataSource!.sort = this.sortForDataSource;
}
ngOnInit() {
this.matTableDataSource!.sort = this.sortForDataSource;
this.matTableDataSource!.paginator = this.paginatorForDataSource;
this.connect();
fromEvent(this.filter.nativeElement, 'keyup')
.pipe(
+13 -7
View File
@@ -1,4 +1,4 @@
import {async, ComponentFixture, TestBed, inject} from '@angular/core/testing';
import {async, ComponentFixture, TestBed, inject, tick, fakeAsync} from '@angular/core/testing';
import {MatPaginatorModule} from './index';
import {MatPaginator, PageEvent} from './paginator';
import {Component, ViewChild} from '@angular/core';
@@ -124,27 +124,33 @@ describe('MatPaginator', () => {
expect(paginator.pageIndex).toBe(0);
expect(component.latestPageEvent ? component.latestPageEvent.pageIndex : null).toBe(0);
});
});
it('should be able to show the first/last buttons', () => {
expect(getFirstButton(fixture))
.toBeNull('Expected first button to not exist.');
.toBeNull('Expected first button to not exist.');
expect(getLastButton(fixture))
.toBeNull('Expected last button to not exist.');
.toBeNull('Expected last button to not exist.');
fixture.componentInstance.showFirstLastButtons = true;
fixture.detectChanges();
expect(getFirstButton(fixture))
.toBeTruthy('Expected first button to be rendered.');
.toBeTruthy('Expected first button to be rendered.');
expect(getLastButton(fixture))
.toBeTruthy('Expected last button to be rendered.');
.toBeTruthy('Expected last button to be rendered.');
});
it('should mark itself as initialized', fakeAsync(() => {
let isMarkedInitialized = false;
paginator.initialized.subscribe(() => isMarkedInitialized = true);
tick();
expect(isMarkedInitialized).toBeTruthy();
}));
describe('when showing the first and last button', () => {
beforeEach(() => {
+9 -1
View File
@@ -20,6 +20,7 @@ import {
} from '@angular/core';
import {Subscription} from 'rxjs';
import {MatPaginatorIntl} from './paginator-intl';
import {HasInitialized, mixinInitialized} from '@angular/material/core';
/** The default page size if there is no page size and there are no provided page size options. */
const DEFAULT_PAGE_SIZE = 50;
@@ -39,6 +40,11 @@ export class PageEvent {
length: number;
}
// Boilerplate for applying mixins to MatPaginator.
/** @docs-private */
export class MatPaginatorBase {}
export const _MatPaginatorBase = mixinInitialized(MatPaginatorBase);
/**
* Component to provide navigation between paged information. Displays the size of the current
* page, user-selectable options to change that size, what items are being shown, and
@@ -56,7 +62,7 @@ export class PageEvent {
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None,
})
export class MatPaginator implements OnInit, OnDestroy {
export class MatPaginator extends _MatPaginatorBase implements OnInit, OnDestroy, HasInitialized {
private _initialized: boolean;
private _intlChanges: Subscription;
@@ -121,12 +127,14 @@ export class MatPaginator implements OnInit, OnDestroy {
constructor(public _intl: MatPaginatorIntl,
private _changeDetectorRef: ChangeDetectorRef) {
super();
this._intlChanges = _intl.changes.subscribe(() => this._changeDetectorRef.markForCheck());
}
ngOnInit() {
this._initialized = true;
this._updateDisplayedPageSizeOptions();
this._markInitialized();
}
ngOnDestroy() {
+9 -1
View File
@@ -7,7 +7,7 @@ import {
wrappedErrorMessage
} from '@angular/cdk/testing';
import {Component, ElementRef, ViewChild} from '@angular/core';
import {async, ComponentFixture, inject, TestBed} from '@angular/core/testing';
import {async, ComponentFixture, fakeAsync, inject, TestBed, tick} from '@angular/core/testing';
import {By} from '@angular/platform-browser';
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
import {Observable} from 'rxjs';
@@ -65,6 +65,14 @@ describe('MatSort', () => {
expect(sortables.size).toBe(0);
});
it('should mark itself as initialized', fakeAsync(() => {
let isMarkedInitialized = false;
component.matSort.initialized.subscribe(() => isMarkedInitialized = true);
tick();
expect(isMarkedInitialized).toBeTruthy();
}));
it('should use the column definition if used within a cdk table', () => {
let cdkTableMatSortAppFixture = TestBed.createComponent(CdkTableMatSortApp);
let cdkTableMatSortAppComponent = cdkTableMatSortAppFixture.componentInstance;
+12 -6
View File
@@ -11,17 +11,18 @@ import {
EventEmitter,
Input,
isDevMode,
Output,
OnChanges,
OnDestroy,
OnInit,
Output,
} from '@angular/core';
import {coerceBooleanProperty} from '@angular/cdk/coercion';
import {CanDisable, mixinDisabled} from '@angular/material/core';
import {CanDisable, HasInitialized, mixinDisabled, mixinInitialized} from '@angular/material/core';
import {SortDirection} from './sort-direction';
import {
getSortInvalidDirectionError,
getSortDuplicateSortableIdError,
getSortHeaderMissingIdError
getSortHeaderMissingIdError,
getSortInvalidDirectionError
} from './sort-errors';
import {Subject} from 'rxjs';
@@ -49,7 +50,7 @@ export interface Sort {
// Boilerplate for applying mixins to MatSort.
/** @docs-private */
export class MatSortBase {}
export const _MatSortMixinBase = mixinDisabled(MatSortBase);
export const _MatSortMixinBase = mixinInitialized(mixinDisabled(MatSortBase));
/** Container for MatSortables to manage the sort state and provide default sort parameters. */
@Directive({
@@ -57,7 +58,8 @@ export const _MatSortMixinBase = mixinDisabled(MatSortBase);
exportAs: 'matSort',
inputs: ['disabled: matSortDisabled']
})
export class MatSort extends _MatSortMixinBase implements CanDisable, OnChanges, OnDestroy {
export class MatSort extends _MatSortMixinBase
implements CanDisable, HasInitialized, OnChanges, OnDestroy, OnInit {
/** Collection of all registered sortables that this directive manages. */
sortables = new Map<string, MatSortable>();
@@ -145,6 +147,10 @@ export class MatSort extends _MatSortMixinBase implements CanDisable, OnChanges,
return sortDirectionCycle[nextDirectionIndex];
}
ngOnInit() {
this._markInitialized();
}
ngOnChanges() {
this._stateChanges.next();
}
+22 -7
View File
@@ -8,10 +8,17 @@
import {_isNumberValue} from '@angular/cdk/coercion';
import {DataSource} from '@angular/cdk/table';
import {
BehaviorSubject,
combineLatest,
merge,
Observable,
of as observableOf,
Subscription
} from 'rxjs';
import {MatPaginator, PageEvent} from '@angular/material/paginator';
import {MatSort, Sort} from '@angular/material/sort';
import {BehaviorSubject, combineLatest, EMPTY, Observable, Subscription} from 'rxjs';
import {map, startWith} from 'rxjs/operators';
import {map} from 'rxjs/operators';
/**
@@ -174,9 +181,17 @@ export class MatTableDataSource<T> extends DataSource<T> {
*/
_updateChangeSubscription() {
// Sorting and/or pagination should be watched if MatSort and/or MatPaginator are provided.
// Otherwise, use an empty observable stream to take their place.
const sortChange: Observable<Sort> = this._sort ? this._sort.sortChange : EMPTY;
const pageChange: Observable<PageEvent> = this._paginator ? this._paginator.page : EMPTY;
// The events should emit whenever the component emits a change or initializes, or if no
// component is provided, a stream with just a null event should be provided.
// The `sortChange` and `pageChange` acts as a signal to the combineLatests below so that the
// pipeline can progress to the next step. Note that the value from these streams are not used,
// they purely act as a signal to progress in the pipeline.
const sortChange: Observable<Sort|null> = this._sort ?
merge<Sort>(this._sort.sortChange, this._sort.initialized) :
observableOf(null);
const pageChange: Observable<PageEvent|null> = this._paginator ?
merge<PageEvent>(this._paginator.page, this._paginator.initialized) :
observableOf(null);
if (this._renderChangesSubscription) {
this._renderChangesSubscription.unsubscribe();
@@ -187,10 +202,10 @@ export class MatTableDataSource<T> extends DataSource<T> {
const filteredData = combineLatest(dataStream, this._filter)
.pipe(map(([data]) => this._filterData(data)));
// Watch for filtered data or sort changes to provide an ordered set of data.
const orderedData = combineLatest(filteredData, sortChange.pipe(startWith(null!)))
const orderedData = combineLatest(filteredData, sortChange)
.pipe(map(([data]) => this._orderData(data)));
// Watch for ordered data or page changes to provide a paged set of data.
const paginatedData = combineLatest(orderedData, pageChange.pipe(startWith(null!)))
const paginatedData = combineLatest(orderedData, pageChange)
.pipe(map(([data]) => this._pageData(data)));
// Watched for paged data changes and send the result to the table to render.
paginatedData.subscribe(data => this._renderData.next(data));
+134 -5
View File
@@ -18,7 +18,9 @@ describe('MatTable', () => {
MatTableApp,
MatTableWithWhenRowApp,
ArrayDataSourceMatTableApp,
NativeHtmlTableApp
NativeHtmlTableApp,
MatTableWithSortApp,
MatTableWithPaginatorApp,
],
}).compileComponents();
}));
@@ -69,7 +71,35 @@ describe('MatTable', () => {
]);
});
describe('with MatTableDataSource', () => {
it('should render with MatTableDataSource and sort', () => {
let fixture = TestBed.createComponent(MatTableWithSortApp);
fixture.detectChanges();
const tableElement = fixture.nativeElement.querySelector('.mat-table')!;
const data = fixture.componentInstance.dataSource!.data;
expectTableToMatchContent(tableElement, [
['Column A', 'Column B', 'Column C'],
[data[0].a, data[0].b, data[0].c],
[data[1].a, data[1].b, data[1].c],
[data[2].a, data[2].b, data[2].c],
]);
});
it('should render with MatTableDataSource and pagination', () => {
let fixture = TestBed.createComponent(MatTableWithPaginatorApp);
fixture.detectChanges();
const tableElement = fixture.nativeElement.querySelector('.mat-table')!;
const data = fixture.componentInstance.dataSource!.data;
expectTableToMatchContent(tableElement, [
['Column A', 'Column B', 'Column C'],
[data[0].a, data[0].b, data[0].c],
[data[1].a, data[1].b, data[1].c],
[data[2].a, data[2].b, data[2].c],
]);
});
describe('with MatTableDataSource and sort/pagination/filter', () => {
let tableElement: HTMLElement;
let fixture: ComponentFixture<ArrayDataSourceMatTableApp>;
let dataSource: MatTableDataSource<TestData>;
@@ -504,14 +534,113 @@ class ArrayDataSourceMatTableApp {
});
}
ngAfterViewInit() {
// Needs to be set up after the view is initialized since the data source will look at the sort
// and paginator's initial values to know what data should be rendered.
ngOnInit() {
this.dataSource!.sort = this.sort;
this.dataSource!.paginator = this.paginator;
}
}
@Component({
template: `
<mat-table [dataSource]="dataSource" matSort>
<ng-container matColumnDef="column_a">
<mat-header-cell *matHeaderCellDef mat-sort-header="a"> Column A</mat-header-cell>
<mat-cell *matCellDef="let row"> {{row.a}}</mat-cell>
</ng-container>
<ng-container matColumnDef="column_b">
<mat-header-cell *matHeaderCellDef> Column B</mat-header-cell>
<mat-cell *matCellDef="let row"> {{row.b}}</mat-cell>
</ng-container>
<ng-container matColumnDef="column_c">
<mat-header-cell *matHeaderCellDef> Column C</mat-header-cell>
<mat-cell *matCellDef="let row"> {{row.c}}</mat-cell>
</ng-container>
<mat-header-row *matHeaderRowDef="columnsToRender"></mat-header-row>
<mat-row *matRowDef="let row; columns: columnsToRender"></mat-row>
</mat-table>
`
})
class MatTableWithSortApp {
underlyingDataSource = new FakeDataSource();
dataSource = new MatTableDataSource<TestData>();
columnsToRender = ['column_a', 'column_b', 'column_c'];
@ViewChild(MatTable) table: MatTable<TestData>;
@ViewChild(MatSort) sort: MatSort;
constructor() {
this.underlyingDataSource.data = [];
// Add three rows of data
this.underlyingDataSource.addData();
this.underlyingDataSource.addData();
this.underlyingDataSource.addData();
this.underlyingDataSource.connect().subscribe(data => {
this.dataSource.data = data;
});
}
ngOnInit() {
this.dataSource!.sort = this.sort;
}
}
@Component({
template: `
<mat-table [dataSource]="dataSource">
<ng-container matColumnDef="column_a">
<mat-header-cell *matHeaderCellDef> Column A</mat-header-cell>
<mat-cell *matCellDef="let row"> {{row.a}}</mat-cell>
</ng-container>
<ng-container matColumnDef="column_b">
<mat-header-cell *matHeaderCellDef> Column B</mat-header-cell>
<mat-cell *matCellDef="let row"> {{row.b}}</mat-cell>
</ng-container>
<ng-container matColumnDef="column_c">
<mat-header-cell *matHeaderCellDef> Column C</mat-header-cell>
<mat-cell *matCellDef="let row"> {{row.c}}</mat-cell>
</ng-container>
<mat-header-row *matHeaderRowDef="columnsToRender"></mat-header-row>
<mat-row *matRowDef="let row; columns: columnsToRender"></mat-row>
</mat-table>
<mat-paginator [pageSize]="5"></mat-paginator>
`
})
class MatTableWithPaginatorApp {
underlyingDataSource = new FakeDataSource();
dataSource = new MatTableDataSource<TestData>();
columnsToRender = ['column_a', 'column_b', 'column_c'];
@ViewChild(MatTable) table: MatTable<TestData>;
@ViewChild(MatPaginator) paginator: MatPaginator;
constructor() {
this.underlyingDataSource.data = [];
// Add three rows of data
this.underlyingDataSource.addData();
this.underlyingDataSource.addData();
this.underlyingDataSource.addData();
this.underlyingDataSource.connect().subscribe(data => {
this.dataSource.data = data;
});
}
ngOnInit() {
this.dataSource!.paginator = this.paginator;
}
}
// Utilities copied from CDKTable's spec
function getElements(element: Element, query: string): Element[] {
return [].slice.call(element.querySelectorAll(query));
@@ -25,11 +25,7 @@ export class TableOverviewExample {
this.dataSource = new MatTableDataSource(users);
}
/**
* Set the paginator and sort after the view init since this component will
* be able to query its view for the initialized paginator and sort.
*/
ngAfterViewInit() {
ngOnInit() {
this.dataSource.paginator = this.paginator;
this.dataSource.sort = this.sort;
}
@@ -15,11 +15,7 @@ export class TablePaginationExample {
@ViewChild(MatPaginator) paginator: MatPaginator;
/**
* Set the paginator after the view init since this component will
* be able to query its view for the initialized paginator.
*/
ngAfterViewInit() {
ngOnInit() {
this.dataSource.paginator = this.paginator;
}
}
@@ -15,11 +15,7 @@ export class TableSortingExample {
@ViewChild(MatSort) sort: MatSort;
/**
* Set the sort after the view init since this component will
* be able to query its view for the initialized sort.
*/
ngAfterViewInit() {
ngOnInit() {
this.dataSource.sort = this.sort;
}
}