Skip to content

Commit

Permalink
feat(paginator): add provider to configure default options (#17595)
Browse files Browse the repository at this point in the history
Adds the `MAT_PAGINATOR_CONFIG` provider that allows consumers to configure the default options for all paginators in a module.

Fixes #17123.
  • Loading branch information
crisbeto authored and jelbourn committed Jan 24, 2020
1 parent ee96e05 commit c7c6262
Show file tree
Hide file tree
Showing 3 changed files with 150 additions and 46 deletions.
138 changes: 94 additions & 44 deletions src/material/paginator/paginator.spec.ts
Original file line number Diff line number Diff line change
@@ -1,45 +1,31 @@
import {async, ComponentFixture, TestBed, inject, tick, fakeAsync} from '@angular/core/testing';
import {Component, ViewChild} from '@angular/core';
import {ComponentFixture, TestBed, tick, fakeAsync} from '@angular/core/testing';
import {Component, ViewChild, Type, Provider} from '@angular/core';
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
import {dispatchMouseEvent} from '@angular/cdk/testing/private';
import {ThemePalette} from '@angular/material/core';
import {MatSelect} from '@angular/material/select';
import {By} from '@angular/platform-browser';
import {MatPaginatorModule, MatPaginator, MatPaginatorIntl} from './index';
import {MAT_PAGINATOR_DEFAULT_OPTIONS, MatPaginatorDefaultOptions} from './paginator';


describe('MatPaginator', () => {
let fixture: ComponentFixture<MatPaginatorApp>;
let component: MatPaginatorApp;
let paginator: MatPaginator;

beforeEach(async(() => {
function createComponent<T>(type: Type<T>, providers: Provider[] = []): ComponentFixture<T> {
TestBed.configureTestingModule({
imports: [
MatPaginatorModule,
NoopAnimationsModule,
],
declarations: [
MatPaginatorApp,
MatPaginatorWithoutPageSizeApp,
MatPaginatorWithoutOptionsApp,
MatPaginatorWithoutInputsApp,
MatPaginatorWithStringValues
],
providers: [MatPaginatorIntl]
imports: [MatPaginatorModule, NoopAnimationsModule],
declarations: [type],
providers: [MatPaginatorIntl, ...providers]
}).compileComponents();
}));

beforeEach(() => {
fixture = TestBed.createComponent(MatPaginatorApp);
const fixture = TestBed.createComponent(type);
fixture.detectChanges();

component = fixture.componentInstance;
paginator = component.paginator;
});
return fixture;
}

describe('with the default internationalization provider', () => {
it('should show the right range text', () => {
const fixture = createComponent(MatPaginatorApp);
const component = fixture.componentInstance;
const rangeElement = fixture.nativeElement.querySelector('.mat-paginator-range-label');

// View second page of list of 100, each page contains 10 items.
Expand Down Expand Up @@ -86,27 +72,32 @@ describe('MatPaginator', () => {
});

it('should show right aria-labels for select and buttons', () => {
const fixture = createComponent(MatPaginatorApp);
const select = fixture.nativeElement.querySelector('.mat-select');
expect(select.getAttribute('aria-label')).toBe('Items per page:');

expect(getPreviousButton(fixture).getAttribute('aria-label')).toBe('Previous page');
expect(getNextButton(fixture).getAttribute('aria-label')).toBe('Next page');
});

it('should re-render when the i18n labels change',
inject([MatPaginatorIntl], (intl: MatPaginatorIntl) => {
const label = fixture.nativeElement.querySelector('.mat-paginator-page-size-label');
it('should re-render when the i18n labels change', () => {
const fixture = createComponent(MatPaginatorApp);
const label = fixture.nativeElement.querySelector('.mat-paginator-page-size-label');
const intl = TestBed.get<MatPaginatorIntl>(MatPaginatorIntl);

intl.itemsPerPageLabel = '1337 items per page';
intl.changes.next();
fixture.detectChanges();
intl.itemsPerPageLabel = '1337 items per page';
intl.changes.next();
fixture.detectChanges();

expect(label.textContent!.trim()).toBe('1337 items per page');
}));
expect(label.textContent!.trim()).toBe('1337 items per page');
});
});

describe('when navigating with the next and previous buttons', () => {
it('should be able to go to the next page', () => {
const fixture = createComponent(MatPaginatorApp);
const component = fixture.componentInstance;
const paginator = component.paginator;
expect(paginator.pageIndex).toBe(0);

dispatchMouseEvent(getNextButton(fixture), 'click');
Expand All @@ -119,6 +110,9 @@ describe('MatPaginator', () => {
});

it('should be able to go to the previous page', () => {
const fixture = createComponent(MatPaginatorApp);
const component = fixture.componentInstance;
const paginator = component.paginator;
paginator.pageIndex = 1;
fixture.detectChanges();
expect(paginator.pageIndex).toBe(1);
Expand All @@ -134,6 +128,7 @@ describe('MatPaginator', () => {
});

it('should be able to show the first/last buttons', () => {
const fixture = createComponent(MatPaginatorApp);
expect(getFirstButton(fixture))
.toBeNull('Expected first button to not exist.');

Expand All @@ -151,6 +146,9 @@ describe('MatPaginator', () => {
});

it('should mark itself as initialized', fakeAsync(() => {
const fixture = createComponent(MatPaginatorApp);
const component = fixture.componentInstance;
const paginator = component.paginator;
let isMarkedInitialized = false;
paginator.initialized.subscribe(() => isMarkedInitialized = true);

Expand All @@ -159,16 +157,24 @@ describe('MatPaginator', () => {
}));

it('should not allow a negative pageSize', () => {
const fixture = createComponent(MatPaginatorApp);
const component = fixture.componentInstance;
const paginator = component.paginator;
paginator.pageSize = -1337;
expect(paginator.pageSize).toBeGreaterThanOrEqual(0);
});

it('should not allow a negative pageIndex', () => {
const fixture = createComponent(MatPaginatorApp);
const component = fixture.componentInstance;
const paginator = component.paginator;
paginator.pageIndex = -42;
expect(paginator.pageIndex).toBeGreaterThanOrEqual(0);
});

it('should be able to set the color of the form field', () => {
const fixture = createComponent(MatPaginatorApp);
const component = fixture.componentInstance;
const formField: HTMLElement = fixture.nativeElement.querySelector('.mat-form-field');

expect(formField.classList).toContain('mat-primary');
Expand All @@ -181,8 +187,14 @@ describe('MatPaginator', () => {
});

describe('when showing the first and last button', () => {
let fixture: ComponentFixture<MatPaginatorApp>;
let component: MatPaginatorApp;
let paginator: MatPaginator;

beforeEach(() => {
fixture = createComponent(MatPaginatorApp);
component = fixture.componentInstance;
paginator = component.paginator;
component.showFirstLastButtons = true;
fixture.detectChanges();
});
Expand Down Expand Up @@ -245,6 +257,9 @@ describe('MatPaginator', () => {
});

it('should mark for check when inputs are changed directly', () => {
const fixture = createComponent(MatPaginatorApp);
const component = fixture.componentInstance;
const paginator = component.paginator;
const rangeElement = fixture.nativeElement.querySelector('.mat-paginator-range-label');

expect(rangeElement.innerText.trim()).toBe('1 – 10 of 100');
Expand All @@ -270,21 +285,23 @@ describe('MatPaginator', () => {
});

it('should default the page size options to the page size if no options provided', () => {
const withoutOptionsAppFixture = TestBed.createComponent(MatPaginatorWithoutOptionsApp);
withoutOptionsAppFixture.detectChanges();
const fixture = createComponent(MatPaginatorWithoutOptionsApp);
fixture.detectChanges();

expect(withoutOptionsAppFixture.componentInstance.paginator._displayedPageSizeOptions)
.toEqual([10]);
expect(fixture.componentInstance.paginator._displayedPageSizeOptions).toEqual([10]);
});

it('should default the page size to the first page size option if not provided', () => {
const withoutPageSizeAppFixture = TestBed.createComponent(MatPaginatorWithoutPageSizeApp);
withoutPageSizeAppFixture.detectChanges();
const fixture = createComponent(MatPaginatorWithoutPageSizeApp);
fixture.detectChanges();

expect(withoutPageSizeAppFixture.componentInstance.paginator.pageSize).toEqual(10);
expect(fixture.componentInstance.paginator.pageSize).toEqual(10);
});

it('should show a sorted list of page size options including the current page size', () => {
const fixture = createComponent(MatPaginatorApp);
const component = fixture.componentInstance;
const paginator = component.paginator;
expect(paginator._displayedPageSizeOptions).toEqual([5, 10, 25, 100]);

component.pageSize = 30;
Expand All @@ -298,6 +315,10 @@ describe('MatPaginator', () => {
});

it('should be able to change the page size while keeping the first item present', () => {
const fixture = createComponent(MatPaginatorApp);
const component = fixture.componentInstance;
const paginator = component.paginator;

// Start on the third page of a list of 100 with a page size of 10.
component.pageIndex = 4;
component.pageSize = 10;
Expand Down Expand Up @@ -339,6 +360,10 @@ describe('MatPaginator', () => {
});

it('should keep track of the right number of pages', () => {
const fixture = createComponent(MatPaginatorApp);
const component = fixture.componentInstance;
const paginator = component.paginator;

component.pageSize = 10;
component.length = 100;
fixture.detectChanges();
Expand All @@ -356,6 +381,10 @@ describe('MatPaginator', () => {
});

it('should show a select only if there are multiple options', () => {
const fixture = createComponent(MatPaginatorApp);
const component = fixture.componentInstance;
const paginator = component.paginator;

expect(paginator._displayedPageSizeOptions).toEqual([5, 10, 25, 100]);
expect(fixture.nativeElement.querySelector('.mat-select')).not.toBeNull();

Expand All @@ -367,17 +396,18 @@ describe('MatPaginator', () => {
});

it('should handle the number inputs being passed in as strings', () => {
const withStringFixture = TestBed.createComponent(MatPaginatorWithStringValues);
withStringFixture.detectChanges();
const fixture = createComponent(MatPaginatorWithStringValues);
fixture.detectChanges();

const withStringPaginator = withStringFixture.componentInstance.paginator;
const withStringPaginator = fixture.componentInstance.paginator;
expect(withStringPaginator.pageIndex).toEqual(0);
expect(withStringPaginator.length).toEqual(100);
expect(withStringPaginator.pageSize).toEqual(10);
expect(withStringPaginator.pageSizeOptions).toEqual([5, 10, 25, 100]);
});

it('should be able to hide the page size select', () => {
const fixture = createComponent(MatPaginatorApp);
const element = fixture.nativeElement;

expect(element.querySelector('.mat-paginator-page-size'))
Expand All @@ -391,6 +421,7 @@ describe('MatPaginator', () => {
});

it('should be able to disable all the controls in the paginator via the binding', () => {
const fixture = createComponent(MatPaginatorApp);
const select: MatSelect =
fixture.debugElement.query(By.directive(MatSelect))!.componentInstance;

Expand All @@ -414,6 +445,25 @@ describe('MatPaginator', () => {
expect(getLastButton(fixture).hasAttribute('disabled')).toBe(true);
});


it('should be able to configure the default options via a provider', () => {
const fixture = createComponent(MatPaginatorWithoutInputsApp, [{
provide: MAT_PAGINATOR_DEFAULT_OPTIONS,
useValue: {
pageSize: 7,
pageSizeOptions: [7, 14, 21],
hidePageSize: true,
showFirstLastButtons: true
} as MatPaginatorDefaultOptions
}]);
const paginator = fixture.componentInstance.paginator;

expect(paginator.pageSize).toBe(7);
expect(paginator.pageSizeOptions).toEqual([7, 14, 21]);
expect(paginator.hidePageSize).toBe(true);
expect(paginator.showFirstLastButtons).toBe(true);
});

});

function getPreviousButton(fixture: ComponentFixture<any>) {
Expand Down
47 changes: 46 additions & 1 deletion src/material/paginator/paginator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ import {
OnInit,
Output,
ViewEncapsulation,
InjectionToken,
Inject,
Optional,
} from '@angular/core';
import {Subscription} from 'rxjs';
import {MatPaginatorIntl} from './paginator-intl';
Expand Down Expand Up @@ -59,6 +62,26 @@ export class PageEvent {
length: number;
}


/** Object that can be used to configure the default options for the paginator module. */
export interface MatPaginatorDefaultOptions {
/** Number of items to display on a page. By default set to 50. */
pageSize?: number;

/** The set of provided page size options to display to the user. */
pageSizeOptions?: number[];

/** Whether to hide the page size selection UI from the user. */
hidePageSize?: boolean;

/** Whether to show the first/last buttons UI to the user. */
showFirstLastButtons?: boolean;
}

/** Injection token that can be used to provide the default options for the paginator module. */
export const MAT_PAGINATOR_DEFAULT_OPTIONS =
new InjectionToken<MatPaginatorDefaultOptions>('MAT_PAGINATOR_DEFAULT_OPTIONS');

// Boilerplate for applying mixins to MatPaginator.
/** @docs-private */
class MatPaginatorBase {}
Expand Down Expand Up @@ -150,9 +173,31 @@ export class MatPaginator extends _MatPaginatorBase implements OnInit, OnDestroy
_displayedPageSizeOptions: number[];

constructor(public _intl: MatPaginatorIntl,
private _changeDetectorRef: ChangeDetectorRef) {
private _changeDetectorRef: ChangeDetectorRef,
@Optional() @Inject(MAT_PAGINATOR_DEFAULT_OPTIONS)
defaults?: MatPaginatorDefaultOptions) {
super();
this._intlChanges = _intl.changes.subscribe(() => this._changeDetectorRef.markForCheck());

if (defaults) {
const {pageSize, pageSizeOptions, hidePageSize, showFirstLastButtons} = defaults;

if (pageSize != null) {
this._pageSize = pageSize;
}

if (pageSizeOptions != null) {
this._pageSizeOptions = pageSizeOptions;
}

if (hidePageSize != null) {
this._hidePageSize = hidePageSize;
}

if (showFirstLastButtons != null) {
this._showFirstLastButtons = showFirstLastButtons;
}
}
}

ngOnInit() {
Expand Down
11 changes: 10 additions & 1 deletion tools/public_api_guard/material/paginator.d.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export declare const MAT_PAGINATOR_DEFAULT_OPTIONS: InjectionToken<MatPaginatorDefaultOptions>;

export declare const MAT_PAGINATOR_INTL_PROVIDER: {
provide: typeof MatPaginatorIntl;
deps: Optional[][];
Expand All @@ -23,7 +25,7 @@ export declare class MatPaginator extends _MatPaginatorBase implements OnInit, O
set pageSizeOptions(value: number[]);
get showFirstLastButtons(): boolean;
set showFirstLastButtons(value: boolean);
constructor(_intl: MatPaginatorIntl, _changeDetectorRef: ChangeDetectorRef);
constructor(_intl: MatPaginatorIntl, _changeDetectorRef: ChangeDetectorRef, defaults?: MatPaginatorDefaultOptions);
_changePageSize(pageSize: number): void;
_nextButtonsDisabled(): boolean;
_previousButtonsDisabled(): boolean;
Expand All @@ -46,6 +48,13 @@ export declare class MatPaginator extends _MatPaginatorBase implements OnInit, O
static ɵfac: i0.ɵɵFactoryDef<MatPaginator>;
}

export interface MatPaginatorDefaultOptions {
hidePageSize?: boolean;
pageSize?: number;
pageSizeOptions?: number[];
showFirstLastButtons?: boolean;
}

export declare class MatPaginatorIntl {
readonly changes: Subject<void>;
firstPageLabel: string;
Expand Down

0 comments on commit c7c6262

Please sign in to comment.