diff --git a/src/demo-app/table/table-demo.html b/src/demo-app/table/table-demo.html
index f25906d05ca4..f9b39b3c6814 100644
--- a/src/demo-app/table/table-demo.html
+++ b/src/demo-app/table/table-demo.html
@@ -172,6 +172,24 @@
MatTable Example
+MatTable with First/Last Buttons Example
+
+
+
+
+
+
+
{{paginatorOutput | json}}
+
+
+
+
+
MatTable Using 'When' Rows for Interactive Details
diff --git a/src/demo-app/table/table-demo.scss b/src/demo-app/table/table-demo.scss
index 71b93a1a4c6d..4f4a2cacf01e 100644
--- a/src/demo-app/table/table-demo.scss
+++ b/src/demo-app/table/table-demo.scss
@@ -141,3 +141,7 @@
background: #f5f5f5;
}
}
+
+.paginator-output {
+ margin-left: 20px;
+}
diff --git a/src/demo-app/table/table-demo.ts b/src/demo-app/table/table-demo.ts
index e1295ff04f79..a2d01d46a194 100644
--- a/src/demo-app/table/table-demo.ts
+++ b/src/demo-app/table/table-demo.ts
@@ -9,7 +9,7 @@
import {Component, ElementRef, ViewChild} from '@angular/core';
import {PeopleDatabase, UserData} from './people-database';
import {PersonDataSource} from './person-data-source';
-import {MatPaginator, MatSort, MatTableDataSource} from '@angular/material';
+import {MatPaginator, MatSort, MatTableDataSource, PageEvent} from '@angular/material';
import {DetailRow, PersonDetailDataSource} from './person-detail-data-source';
import {animate, state, style, transition, trigger} from '@angular/animations';
import {SelectionModel} from '@angular/cdk/collections';
@@ -65,6 +65,8 @@ export class TableDemo {
@ViewChild('paginatorForDataSource') paginatorForDataSource: MatPaginator;
@ViewChild('sortForDataSource') sortForDataSource: MatSort;
+ paginatorOutput: PageEvent;
+
constructor(public _peopleDatabase: PeopleDatabase) {
this.matTableDataSource.sortingDataAccessor = (data: UserData, property: string) => {
switch (property) {
@@ -183,4 +185,8 @@ export class TableDemo {
toggleHighlight(property: string, enable: boolean) {
enable ? this.highlights.add(property) : this.highlights.delete(property);
}
+
+ handlePaginator(pageEvent: PageEvent) {
+ this.paginatorOutput = pageEvent;
+ }
}
diff --git a/src/lib/paginator/_paginator-theme.scss b/src/lib/paginator/_paginator-theme.scss
index ff7258557f49..3082d9472bf2 100644
--- a/src/lib/paginator/_paginator-theme.scss
+++ b/src/lib/paginator/_paginator-theme.scss
@@ -16,15 +16,22 @@
color: mat-color($foreground, secondary-text);
}
- .mat-paginator-increment,
- .mat-paginator-decrement {
+ .mat-paginator-decrement,
+ .mat-paginator-increment {
border-top: 2px solid mat-color($foreground, 'icon');
border-right: 2px solid mat-color($foreground, 'icon');
}
+ .mat-paginator-first,
+ .mat-paginator-last {
+ border-top: 2px solid mat-color($foreground, 'icon');
+ }
+
.mat-icon-button[disabled] {
+ .mat-paginator-decrement,
.mat-paginator-increment,
- .mat-paginator-decrement {
+ .mat-paginator-first,
+ .mat-paginator-last {
border-color: mat-color($foreground, 'disabled');
}
}
diff --git a/src/lib/paginator/paginator-intl.ts b/src/lib/paginator/paginator-intl.ts
index 531e3aac9d22..a235c980c080 100644
--- a/src/lib/paginator/paginator-intl.ts
+++ b/src/lib/paginator/paginator-intl.ts
@@ -30,6 +30,12 @@ export class MatPaginatorIntl {
/** A label for the button that decrements the current page. */
previousPageLabel: string = 'Previous page';
+ /** A label for the button that moves to the first page. */
+ firstPageLabel: string = 'First page';
+
+ /** A label for the button that moves to the last page. */
+ lastPageLabel: string = 'Last page';
+
/** A label for the range of items within the current page and the length of the whole list. */
getRangeLabel = (page: number, pageSize: number, length: number) => {
if (length == 0 || pageSize == 0) { return `0 of ${length}`; }
diff --git a/src/lib/paginator/paginator.html b/src/lib/paginator/paginator.html
index 11bc5d24e7e1..4d28fb721d43 100644
--- a/src/lib/paginator/paginator.html
+++ b/src/lib/paginator/paginator.html
@@ -25,6 +25,17 @@
{{_intl.getRangeLabel(pageIndex, pageSize, length)}}
+
+
diff --git a/src/lib/paginator/paginator.md b/src/lib/paginator/paginator.md
index 7284bb8829eb..4359b57dc7a1 100644
--- a/src/lib/paginator/paginator.md
+++ b/src/lib/paginator/paginator.md
@@ -26,4 +26,4 @@ This will allow you to change the following:
3. The tooltip messages on the navigation buttons.
### Accessibility
-The `aria-label`s for next page and previous page buttons can be set in `MatPaginatorIntl`.
+The `aria-label`s for next page, previous page, first page and last page buttons can be set in `MatPaginatorIntl`.
diff --git a/src/lib/paginator/paginator.scss b/src/lib/paginator/paginator.scss
index c10a51d578df..11a9bcc0556d 100644
--- a/src/lib/paginator/paginator.scss
+++ b/src/lib/paginator/paginator.scss
@@ -15,8 +15,18 @@ $mat-paginator-button-margin: 8px;
$mat-paginator-button-icon-height: 8px;
$mat-paginator-button-icon-width: 8px;
-$mat-paginator-button-decrement-icon-margin: 12px;
-$mat-paginator-button-increment-icon-margin: 16px;
+$mat-paginator-button-increment-icon-margin: 12px;
+$mat-paginator-button-decrement-icon-margin: 16px;
+
+$mat-paginator-button-first-last-icon-width: 14px;
+
+$mat-paginator-button-first-icon-margin: 3px;
+$mat-paginator-button-last-icon-margin: 15px;
+
+
+$mat-paginator-button-first-decrement-icon-margin: 21px;
+$mat-paginator-button-last-increment-icon-margin: 9px;
+
.mat-paginator {
display: block;
@@ -50,7 +60,7 @@ $mat-paginator-button-increment-icon-margin: 16px;
margin: $mat-paginator-range-label-margin;
}
-.mat-paginator-increment-button + .mat-paginator-increment-button {
+.mat-paginator-decrement-button + .mat-paginator-decrement-button {
margin: 0 0 0 $mat-paginator-button-margin;
[dir='rtl'] & {
@@ -58,21 +68,28 @@ $mat-paginator-button-increment-icon-margin: 16px;
}
}
-.mat-paginator-increment,
-.mat-paginator-decrement {
+.mat-paginator-decrement,
+.mat-paginator-increment {
width: $mat-paginator-button-icon-width;
height: $mat-paginator-button-icon-height;
}
-.mat-paginator-decrement,
-[dir='rtl'] .mat-paginator-increment {
- transform: rotate(45deg);
-}
.mat-paginator-increment,
[dir='rtl'] .mat-paginator-decrement {
+ transform: rotate(45deg);
+}
+.mat-paginator-decrement,
+[dir='rtl'] .mat-paginator-increment {
transform: rotate(225deg);
}
+.mat-paginator-increment {
+ margin-left: $mat-paginator-button-increment-icon-margin;
+ [dir='rtl'] & {
+ margin-right: $mat-paginator-button-increment-icon-margin;
+ }
+}
+
.mat-paginator-decrement {
margin-left: $mat-paginator-button-decrement-icon-margin;
[dir='rtl'] & {
@@ -80,13 +97,34 @@ $mat-paginator-button-increment-icon-margin: 16px;
}
}
-.mat-paginator-increment {
- margin-left: $mat-paginator-button-increment-icon-margin;
- [dir='rtl'] & {
- margin-right: $mat-paginator-button-increment-icon-margin;
+.mat-paginator-first {
+ transform: rotate(90deg);
+ width: $mat-paginator-button-first-last-icon-width;
+ height: $mat-paginator-button-icon-height;
+ float:left;
+ margin-left: $mat-paginator-button-first-icon-margin;
+}
+
+.mat-paginator-navigation-first {
+ .mat-paginator-decrement {
+ margin-left: $mat-paginator-button-first-decrement-icon-margin;
}
}
+.mat-paginator-navigation-last {
+ .mat-paginator-increment {
+ float: left;
+ margin-left: $mat-paginator-button-last-increment-icon-margin;
+ }
+}
+
+.mat-paginator-last {
+ transform: rotate(90deg);
+ width: $mat-paginator-button-first-last-icon-width;
+ height: $mat-paginator-button-icon-height;
+ margin-left: $mat-paginator-button-last-icon-margin;
+}
+
.mat-paginator-range-actions {
display: flex;
align-items: center;
diff --git a/src/lib/paginator/paginator.spec.ts b/src/lib/paginator/paginator.spec.ts
index cccd55300c26..504615e32297 100644
--- a/src/lib/paginator/paginator.spec.ts
+++ b/src/lib/paginator/paginator.spec.ts
@@ -104,7 +104,7 @@ describe('MatPaginator', () => {
}));
});
- describe('when navigating with the navigation buttons', () => {
+ describe('when navigating with the next and previous buttons', () => {
it('should be able to go to the next page', () => {
expect(paginator.pageIndex).toBe(0);
@@ -125,6 +125,58 @@ describe('MatPaginator', () => {
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.');
+
+ expect(getLastButton(fixture))
+ .toBeNull('Expected last button to not exist.');
+
+ fixture.componentInstance.showFirstLastButtons = true;
+ fixture.detectChanges();
+
+ expect(getFirstButton(fixture))
+ .toBeTruthy('Expected first button to be rendered.');
+
+ expect(getLastButton(fixture))
+ .toBeTruthy('Expected last button to be rendered.');
+
+ });
+
+ describe('when showing the first and last button', () => {
+
+ beforeEach(() => {
+ component.showFirstLastButtons = true;
+ fixture.detectChanges();
+ });
+
+ it('should show right aria-labels for first/last buttons', () => {
+ expect(getFirstButton(fixture).getAttribute('aria-label')).toBe('First page');
+ expect(getLastButton(fixture).getAttribute('aria-label')).toBe('Last page');
+ });
+
+ it('should be able to go to the last page via the last page button', () => {
+ expect(paginator.pageIndex).toBe(0);
+
+ dispatchMouseEvent(getLastButton(fixture), 'click');
+
+ expect(paginator.pageIndex).toBe(9);
+ expect(component.latestPageEvent ? component.latestPageEvent.pageIndex : null).toBe(9);
+ });
+
+ it('should be able to go to the first page via the first page button', () => {
+ paginator.pageIndex = 3;
+ fixture.detectChanges();
+ expect(paginator.pageIndex).toBe(3);
+
+ dispatchMouseEvent(getFirstButton(fixture), 'click');
+
+ expect(paginator.pageIndex).toBe(0);
+ expect(component.latestPageEvent ? component.latestPageEvent.pageIndex : null).toBe(0);
+ });
+
it('should disable navigating to the next page if at last page', () => {
component.goToLastPage();
fixture.detectChanges();
@@ -148,6 +200,7 @@ describe('MatPaginator', () => {
expect(component.latestPageEvent).toBe(null);
expect(paginator.pageIndex).toBe(0);
});
+
});
it('should mark for check when inputs are changed directly', () => {
@@ -253,7 +306,7 @@ describe('MatPaginator', () => {
expect(fixture.nativeElement.querySelector('.mat-select')).toBeNull();
});
- it('should handle the number inputs being passed in as strings', () => {
+ it('should handle the number inputs being passed in as strings', () => {
const withStringFixture = TestBed.createComponent(MatPaginatorWithStringValues);
const withStringPaginator = withStringFixture.componentInstance.paginator;
@@ -277,6 +330,7 @@ describe('MatPaginator', () => {
expect(element.querySelector('.mat-paginator-page-size'))
.toBeNull('Expected select to be removed.');
});
+
});
function getPreviousButton(fixture: ComponentFixture) {
@@ -287,12 +341,21 @@ function getNextButton(fixture: ComponentFixture) {
return fixture.nativeElement.querySelector('.mat-paginator-navigation-next');
}
+function getFirstButton(fixture: ComponentFixture) {
+ return fixture.nativeElement.querySelector('.mat-paginator-navigation-first');
+}
+
+function getLastButton(fixture: ComponentFixture) {
+ return fixture.nativeElement.querySelector('.mat-paginator-navigation-last');
+}
+
@Component({
template: `
@@ -303,6 +366,7 @@ class MatPaginatorApp {
pageSize = 10;
pageSizeOptions = [5, 10, 25, 100];
hidePageSize = false;
+ showFirstLastButtons = false;
length = 100;
latestPageEvent: PageEvent | null;
diff --git a/src/lib/paginator/paginator.ts b/src/lib/paginator/paginator.ts
index e4e44636ed0f..95dfebc37c5d 100644
--- a/src/lib/paginator/paginator.ts
+++ b/src/lib/paginator/paginator.ts
@@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {coerceNumberProperty} from '@angular/cdk/coercion';
+import {coerceNumberProperty, coerceBooleanProperty} from '@angular/cdk/coercion';
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
@@ -100,6 +100,14 @@ export class MatPaginator implements OnInit, OnDestroy {
/** Whether to hide the page size selection UI from the user. */
@Input() hidePageSize = false;
+ /** Whether to show the first/last buttons UI to the user. */
+ @Input()
+ get showFirstLastButtons(): boolean { return this._showFirstLastButtons; }
+ set showFirstLastButtons(value: boolean) {
+ this._showFirstLastButtons = coerceBooleanProperty(value);
+ }
+ private _showFirstLastButtons = false;
+
/** Event emitted when the paginator changes the page size or page index. */
@Output() readonly page = new EventEmitter();
@@ -134,6 +142,22 @@ export class MatPaginator implements OnInit, OnDestroy {
this._emitPageEvent();
}
+ /** Move to the first page if not already there. */
+ firstPage(): void {
+ // hasPreviousPage being false implies at the start
+ if (!this.hasPreviousPage()) { return; }
+ this.pageIndex = 0;
+ this._emitPageEvent();
+ }
+
+ /** Move to the last page if not already there. */
+ lastPage(): void {
+ // hasNextPage being false implies at the end
+ if (!this.hasNextPage()) { return; }
+ this.pageIndex = this.getNumberOfPages();
+ this._emitPageEvent();
+ }
+
/** Whether there is a previous page. */
hasPreviousPage(): boolean {
return this.pageIndex >= 1 && this.pageSize != 0;
@@ -141,10 +165,16 @@ export class MatPaginator implements OnInit, OnDestroy {
/** Whether there is a next page. */
hasNextPage(): boolean {
- const numberOfPages = Math.ceil(this.length / this.pageSize) - 1;
+ const numberOfPages = this.getNumberOfPages();
return this.pageIndex < numberOfPages && this.pageSize != 0;
}
+ /** Calculate the number of pages */
+ getNumberOfPages(): number {
+ return Math.ceil(this.length / this.pageSize) - 1;
+ }
+
+
/**
* Changes the page size so that the first item displayed on the page will still be
* displayed using the new page size.
diff --git a/src/material-examples/table-pagination/table-pagination-example.html b/src/material-examples/table-pagination/table-pagination-example.html
index ff362ebab4c4..cea5b5f235f7 100644
--- a/src/material-examples/table-pagination/table-pagination-example.html
+++ b/src/material-examples/table-pagination/table-pagination-example.html
@@ -31,6 +31,7 @@
+ [pageSizeOptions]="[5, 10, 20]"
+ [showFirstLastButtons]="true">