mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
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:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user