components
components copied to clipboard
[Paginator] Allow 'All' option
Feature request
What is the expected behavior?
Ability to provide "ALL" as page size option.[5,10,50, All]
What is the current behavior?
Page size options are fixed numbers.[5,10,50]
What is the use-case or motivation for changing an existing behavior?
Most paginators offer this function, therefore user expects it.
Which versions of Angular, Material, OS, TypeScript, browsers are affected?
All
@andrewseguin When are you planning to implement this functionality and are there any solutions?
Its on our roadmap but we haven't yet planned it for implementation.
This works, but isn't great:
<mat-paginator [pageSizeOptions]="getPageSizeOptions()"></mat-paginator>
then in code:
getPageSizeOptions(): number[] {
return [5, 10, 20, myTableDataSourceArray.length];
}
Labels for the entries would be nice, something like
[pageSizeOptions]="[5, 10, 20, 57]"
[pageSizeLabels]="[5, 10, 20, ALL]"
I've implemented a solution locally, but I've never actually contributed to Material2 before. I've read through the CONTRIBUTING.md file and it says to search for existing issues. Doing so resulted in finding this issue so I would assume I should not create a new issue for my contribution. What are the appropriate next steps? Should I simply attach my contribution here for review? Thanks
@jeff-a-andersen
- Fork this repo
- Clone your fork into your local machine
- Checkout a new branch in your clone
- Do the required changes in the new branch
- Commit and push to your fork
- Submit a pull request
Further details in the contributing guide
Is this still planned?
I'm still planning to make the change, just keep getting pulled away for my daily work..
+1
+1 I need this feature as well. Using an ugly workaround for now.
Has this been implemented yet?
I did make the changes to my copy of the material2 repository about a year ago. I didn't have time to jump through the hoops to get it released at the time. Since then, we've moved to using PrimeNG so my changes sat dormant because it was less of a priority for me. If you're interested in taking this up, you can see what I changed in the following commits:
https://github.com/jeff-a-andersen/material2/commit/5b185510f28c98d97b39f254c95ad5125b3f8c26 https://github.com/jeff-a-andersen/material2/commit/4b7f10fb102745eb2fd9fe587a25922d213dd1ba
If I can ever find time to jump back into it, I will.
+1 I need this feature as well. Using an ugly workaround for now.
What kind of workaround are you using?
+1 I need this feature as well. Using an ugly workaround for now.
What kind of workaround are you using? @dilyandimitrov I'm using a very large number to mean "all" e.g. [5, 10, 20, 1000] Obviously this is not clean, nice or safe. Have you seen the proposed solution by @rramsey above? To be honest, his workaround looks better than mine.
Hey, guys... no update here yet?
I used a solution from stackoverflow: https://stackoverflow.com/questions/47588162/how-to-add-all-option-for-angular-material-paginator It's similar to @rramsey 's solution, but the styling part was missing.
To make the solution work I needed to add encapsulation: ViewEncapsulation.None
inside of @Component
decorator.
The only problem I got is that through ViewEncapsulation.None the "All" Option appears in every component. Yes, that's what ViewEncapsulation.None is made for.
Does anybody have another solution to make he "All" thing work in a specific component without using ViewEncapsulation.None?
Hey, @KevCanon . I did it, but not in a pretty good way, it worked tho. So, I changed directly the DOM by manipulating a high number (because I needed it to be translated according to the culture) and I used the pageEvent:
this.textALL = this.translate.instant('All');
addOptionAllToPaginator(): void {
const element: NodeListOf<Element> = this.window.document.querySelectorAll('[role="option"]');
if (element !== null && element !== undefined) {
for (let i = 0; i < element.length; i++) {
const optText = element[i].getElementsByClassName('mat-option-text');
if (optText !== null && optText !== undefined && optText.length === 1) {
const text: HTMLSpanElement = optText[0] as HTMLSpanElement;
const textWithoutSpace = text.innerText.replace(/\s/g, '');
if (Number(textWithoutSpace) && textWithoutSpace === '9999') {
text.innerHTML = this.textALL;
}
}
}
}
}
pageEventChanged(pageEvent: PageEvent): void {
if (this.previousPageSize !== pageEvent.pageSize) {
const ariaLabelTranslated: string = this.paginator._intl.itemsPerPageLabel;
const element: NodeListOf<Element> = this.window.document.querySelectorAll(`[aria-label="${ariaLabelTranslated}"]`);
if (element !== null && element !== undefined) {
for (let i = 0; i < element.length; i++) {
const optText: HTMLCollectionOf<Element> = element[i].getElementsByClassName('mat-select-value-text');
if (optText !== null && optText !== undefined && optText.length === 1) {
const text: HTMLSpanElement = optText[0] as HTMLSpanElement;
if (Number(text.innerText) && pageEvent.pageSize === 9999) {
text.innerHTML = this.textALL;
} else {
text.innerHTML = pageEvent.pageSize.toString();
}
}
}
}
this.previousPageSize = pageEvent.pageSize;
}
}
getPageSizeOptions(): number[] {
const pageSizeOptions: number[] = [5, 10, 25];
if (this.templates !== null && this.templates.length > 0) {
pageSizeOptions.push(9999);
this.addOptionAllToPaginator();
}
return pageSizeOptions;
}```
@andrewseguin I've been waiting for this implementation for 2 years, when the update?))
I submitted #20367 based on @jeff-a-andersen's solution... fingers crossed!
I submitted a more focus solution: #20397.
@Lavdaft I'm using a similar approach but it broke somehow after upgrading angular 8 to 11. We are now facing an issue when the default value is already "All". Did you manage to make your solution work in this scenario?
When the default is already "All", we show 9999 in the text selected.
And if we force the selected text to "All", it never gets back to a number even after further selections.
My solution does work as expected even when you start with a length that is equal to your page size (with label "All"). For example: in my typescript I set the following properties:
length = 100;
pageSize = 100;
pageIndex = 0;
pageSizeOptions: {[key: string]: string} = {5: '5', 10: '10', 25: '25,' 100: 'All'};
And in my HTML:
<mat-paginator #paginator
(page)="handlePageEvent($event)"
[length]="length"
[pageSize]="pageSize"
[pageSizeOptions]="showPageSizeOptions ? pageSizeOptions : {}"
[pageIndex]="pageIndex">
</mat-paginator>
In this scenario, the page size will be defaulted to "All", and I can successfully change it to 5, 10 or 25.
I developed the solution on Angular 10.
My solution does work as expected even when you start with a length that is equal to your page size (with label "All"). For example: in my typescript I set the following properties:
length = 100; pageSize = 100; pageIndex = 0; pageSizeOptions: {[key: string]: string} = {5: '5', 10: '10', 25: '25,' 100: 'All'};
And in my HTML:
<mat-paginator #paginator (page)="handlePageEvent($event)" [length]="length" [pageSize]="pageSize" [pageSizeOptions]="showPageSizeOptions ? pageSizeOptions : {}" [pageIndex]="pageIndex"> </mat-paginator>
In this scenario, the page size will be defaulted to "All", and I can successfully change it to 5, 10 or 25.
I developed the solution on Angular 10.
What is "showPageSizeOptions"? Can you show its code?
showPageSizeOptions
is simply a boolean property found in dev-app contained in the the Angular Material components source code.
You can download the source code and run dev-app
to see demos of the components with the ability to adjust various options on the fly. In the case of mat-paginator
, the `showPageSizeOptions in the paginator-demo.ts file supports a toggle in the paginator demo page that allows you to show/hide the dropdown list of page size options.
dbgod, thank you, I viewed this document. I need to do label for page size option, that shows all elements. I can do it with variable that shows number on front-end, but I need to show label "All".
Your decision - "pageSizeOptions: {[key: string]: string} = {5: '5', 10: '10', 25: '25', 100: 'All'};" looks good and this is that what I looked for. But it doesn't work with my code. I work with Angular 11, not sure that it's matter in this case.
Here is my code, maybe you can see and tell what is wrong:
HTML:
<mat-paginator
(page)="pageEvent = $event; onPaginateChange($event)"
[length]="numberPatientTests"
[pageSize]="defaultTestsPerPage"
[disabled]="disabled"
[showFirstLastButtons]="showFirstLastButtons"
[pageSizeOptions]="showPageSizeOptions ? pageSizeOptions : {}"
[hidePageSize]="hidePageSize"
[pageIndex]="pageIndex">
</mat-paginator>
TS:
numberPatientTests = this.tests.length;
defaultTestsPerPage: any = 5;
filteredTests: any[] = [];
pageSizeOptions: {[key: string]: string} = {5: '5', 10: '10', 25: '25', 100: '100', 1000: 'All'};
pageIndex = 0;
pageEvent: PageEvent;
hidePageSize = false;
showPageSizeOptions = true;
showFirstLastButtons = true;
disabled = false;
onPaginateChange(data: PageEvent) {
this.filteredTests = this.tests.slice(data.pageIndex*data.pageSize, data.pageIndex*data.pageSize + data.pageSize);
}
Is there any movement on this, by chance? I've had some teams asking for this recently, and the possible workarounds don't really work very well. Doesn't seem to be a straightforward way to accomplish this use case, as it stands today.
I made a not most beautiful solution, but it works perfectly. My API accept 999 number as 'All', so I just change the styles to make it shows the 'All' text on the component.
styles.css
mat-option[ng-reflect-value='999']:before {
content: 'All';
float: left;
text-transform: none;
top: 0px;
position: relative;
}
mat-option[ng-reflect-value='999'] .mat-option-text {
display: none;
position: relative;
}
mat-select[ng-reflect-value='999'] .mat-select-value-text span {
display: none;
}
mat-select[ng-reflect-value='999'] .mat-select-value-text:after {
content: 'All';
}
pageSize
pageSize = [5,10,25,50,100,999]
The problem with all those solutions is that the "ALL" is hardcoded. We need a solution that accepts translation/culture change...
I made a not most beautiful solution, but it works perfectly. My API accept 999 number as 'All', so I just change the styles to make it shows the 'All' text on the component.
styles.css
mat-option[ng-reflect-value='999']:before { content: 'All'; float: left; text-transform: none; top: 0px; position: relative; } mat-option[ng-reflect-value='999'] .mat-option-text { display: none; position: relative; } mat-select[ng-reflect-value='999'] .mat-select-value-text span { display: none; } mat-select[ng-reflect-value='999'] .mat-select-value-text:after { content: 'All'; }
pageSize
pageSize = [5,10,25,50,100,999]
Unfortunately, ng-reflect-value
isn't available in a production environment. I'm still searching for a way to select the "all" option in CSS without using ng-reflect-value
and without using :last-child
which applies changes to all select
components.
A translatable version would be great, but for now, I'd even be happy with a CSS hack like this that works in production.
I think I managed a temporarily solution that should work also for translatable "All" label. It works by wrapping some of private methods and direct DOM manipulation.
const allValue = 999999;
const allValueLabel = 'All'; // or inject i18n service and get translation of "All" in directive constructor
@Directive({
selector: '[appAddAllToPaginator]',
})
export class AddAllToPaginator {
public constructor(private readonly host: MatPaginator, private readonly elRef: ElementRef) {
// @ts-ignore
const proxiedUpdateDisplayedPageSizeOptions = host._updateDisplayedPageSizeOptions.bind(host);
// @ts-ignore
host._updateDisplayedPageSizeOptions = () => {
proxiedUpdateDisplayedPageSizeOptions();
// @ts-ignore
const displayedPageSizeOptions = host._displayedPageSizeOptions;
if (!displayedPageSizeOptions) return;
const newDisplayedPageSizeOptions = [
...displayedPageSizeOptions.filter((x) => x !== allValue),
allValueLabel,
];
// @ts-ignore
host._displayedPageSizeOptions = newDisplayedPageSizeOptions;
// @ts-ignore
host._changeDetectorRef.markForCheck();
};
const proxiedChangePageSize = host._changePageSize.bind(host);
host._changePageSize = (v) => {
// converting to number if value is string. We need to do so because whole paging logic depend that pageSize is a number.
// @ts-ignore
if (v === allValueLabel) v = allValue;
proxiedChangePageSize(v);
elRef.nativeElement.querySelector('.mat-select-value').innerText = v === allValue ? allValueLabel : v;
};
}
}
Usage:
<!-- we dont need to modify pageSizeOptions. 'All' option is appended automatically -->
<mat-paginator
[length]="10000"
[pageSize]="10"
[pageSizeOptions]="[5, 10, 25, 100]"
aria-label="Select page"
appAddAllToPaginator>
</mat-paginator>
Working demo: https://stackblitz.com/edit/angular-wy4dmj?file=src%2Fapp%2Fadd-all-to-paginator.directive.ts
It should be also easily convertible to also use some lookup ({ [id: number]: string }
) to translate each label entry in case if you need to translate other labels also.
Note that that solution can easily break up if someone changes private methods names, but that should be easily fixable. Tested on angular material 14.1.1 & 12.0
I think I managed a temporarily solution that should work also for translatable "All" label. It works by wrapping some of private methods and direct DOM manipulation.
const allValue = 999999; const allValueLabel = 'All'; // or inject i18n service and get translation of "All" in directive constructor @Directive({ selector: '[appAddAllToPaginator]', }) export class AddAllToPaginator { public constructor(private readonly host: MatPaginator, private readonly elRef: ElementRef) { // @ts-ignore const proxiedUpdateDisplayedPageSizeOptions = host._updateDisplayedPageSizeOptions.bind(host); // @ts-ignore host._updateDisplayedPageSizeOptions = () => { proxiedUpdateDisplayedPageSizeOptions(); // @ts-ignore const displayedPageSizeOptions = host._displayedPageSizeOptions; if (!displayedPageSizeOptions) return; const newDisplayedPageSizeOptions = [ ...displayedPageSizeOptions.filter((x) => x !== allValue), allValueLabel, ]; // @ts-ignore host._displayedPageSizeOptions = newDisplayedPageSizeOptions; // @ts-ignore host._changeDetectorRef.markForCheck(); }; const proxiedChangePageSize = host._changePageSize.bind(host); host._changePageSize = (v) => { // converting to number if value is string. We need to do so because whole paging logic depend that pageSize is a number. // @ts-ignore if (v === allValueLabel) v = allValue; proxiedChangePageSize(v); elRef.nativeElement.querySelector('.mat-select-value').innerText = v === allValue ? allValueLabel : v; }; } }
Usage:
<!-- we dont need to modify pageSizeOptions. 'All' option is appended automatically --> <mat-paginator [length]="10000" [pageSize]="10" [pageSizeOptions]="[5, 10, 25, 100]" aria-label="Select page" appAddAllToPaginator> </mat-paginator>
Working demo: https://stackblitz.com/edit/angular-wy4dmj?file=src%2Fapp%2Fadd-all-to-paginator.directive.ts
It should be also easily convertible to also use some lookup (
{ [id: number]: string }
) to translate each label entry in case if you need to translate other labels also.Note that that solution can easily break up if someone changes private methods names, but that should be easily fixable. Tested on angular material 14.1.1 & 12.0
Works Like charm @malciin Thankyou for your effort and now i understood the real power of angular built-in features like directives, pipes etc,
in my scenario
i have a input field to search items in the table and the input field is default state is disabled when the user clicks the length of the datasource in the paginator then only search option will work
<input
[disabled]="paginator.pageSize == dataSource.data.length? false : true"
matInput
(keyup)="applyFilter($event)"
#input
/>
i have used your custom directive code and used the Input decorator on it @Input() allValue:number; and in the paginator used these. <mat-paginator [pageSizeOptions]="[5, 10, 20]" [pageSize]="5" [allValue]="dataSource.data.length" appAddAllToPaginator >