Angular: MatPaginator Custom Styling – DEV Community

بهعنوان یک توسعهدهنده فرانتاند، احتمالاً وظیفهای برای نمایش دادهها در یک جدول داشتهاید و بهعنوان یک توسعهدهنده فرانتاند، احتمالاً Angular Material را برای این کار انتخاب کردهاید.
در مقاله قبلی خود Angular: Infinite Scrolling اشاره کردم که چگونه می توانید جدول بی نهایت را با چند عنصر اولیه شروع کنید، اما علیرغم اینکه یک راه حل UX خوب است، گاهی اوقات مجبوریم صفحه بندی سنتی را انجام دهیم.
هنگامی که MatPaginator را جستجو می کنید، راه حل خود را پیدا می کنید، با این حال، یک نکته قابل توجه است… صفحه بندی Angular اصلا جذاب به نظر نمی رسد. آیا خوب نیست اگر بتوانیم ناوبری آن را حفظ کنیم، اما جلوه های بصری آن را به چیز دیگری تغییر دهیم؟
هدف
در مقاله زیر به صورت گام به گام توضیح خواهیم داد که چگونه می توانیم یک دستورالعمل سفارشی را پیاده سازی کنیم و آن را به آن پیوست کنیم mat-paginator
که به طور کامل رابط کاربری خود را به مثال زیر تغییر می دهد.
کل کد منبع در StackBlitz موجود است.
ما یک دستورالعمل به نام ایجاد خواهیم کرد appBubblePagination
، که از قدرت استفاده می کند Renderer2
برای ایجاد عناصر UI سفارشی جایگزین طرح صفحهبندی پیشفرض، و استفاده از دستورالعمل به صورت زیر خواهد بود:
<table mat-table [dataSource]="dataSource" [trackBy]="identity">
<!-- table content -->
</table>
<mat-paginator
appBubblePagination
[appCustomLength]="dataSource.data.length"
[length]="dataSource.data.length"
[pageSize]="20"
>
</mat-paginator>
مرحله 1.) یک دستورالعمل ایجاد کنید
برای دستیابی به هدف تغییر جنبه بصری صفحه بندی مت، دستورالعملی ایجاد کرده و وابستگی های زیر را وارد می کنیم.
@Directive({
selector: '[appBubblePagination]',
standalone: true,
})
export class BubblePaginationDirective {
constructor(
@Host() @Self() @Optional() private readonly matPag: MatPaginator,
private elementRef: ElementRef,
private ren: Renderer2
) {}
}
وابستگی ها به صورت زیر استفاده می شوند:
-
MatPaginator
– ارجاع به پیوست استmat-paginator
. برای تغییر دستی فهرست صفحه بندی با کلیک بر روی حباب های سفارشی استفاده می شود. -
ElementRef
– ارجاع به عناصر DOM است (mat-paginator
) که بخشنامه به آن پیوست شده است. از آن برای به دست آوردن مرجعی که در آن عناصر HTML اضافی ارائه می شود استفاده می شود. در مورد Angular docs بیشتر بخوانید. -
Renderer2
– امکان رندر کردن عناصر HTML، افزودن/حذف کلاس های css و پیوست کردن شنوندگان (کلیک کنید، شناور کنید). در مورد Angular docs بیشتر بخوانید.
مرحله 2.) طرح بندی پیش فرض را تغییر دهید
بعد، می خواهیم کمی جنبه بصری صفحه بندی پیش فرض را تغییر دهیم. در زیر تصویری از حالت شروع و پایان وجود دارد.
تغییرات شامل:
- حذف متن “اقلام در صفحه”.
- شماره صفحه بندی فعلی (1-20) را در سمت راست قرار دهید و تصاویر آن را اصلاح کنید
با وابستگی ها ElementRef
و Renderer2
، ما طرح را به صورت زیر تغییر می دهیم:
export class BubblePaginationDirective implements AfterViewInit {
ngAfterViewInit(): void {
this.styleDefaultPagination();
}
private styleDefaultPagination() {
const nativeElement = this.elementRef.nativeElement;
const itemsPerPage = nativeElement.querySelector(
'.mat-mdc-paginator-page-size'
);
const howManyDisplayedEl = nativeElement.querySelector(
'.mat-mdc-paginator-range-label'
);
// remove 'items per page'
this.ren.setStyle(itemsPerPage, 'display', 'none');
// style text of how many elements are currently displayed
this.ren.setStyle(howManyDisplayedEl, 'position', 'absolute');
this.ren.setStyle(howManyDisplayedEl, 'left', '0');
this.ren.setStyle(howManyDisplayedEl, 'color', '#919191');
this.ren.setStyle(howManyDisplayedEl, 'font-size', '14px');
}
}
مرحله 3. یک DIV بین دکمه ناوبری وارد کنید
با بررسی عنصر HTML می بینیم که یک وجود دارد mat-mdc-paginator-range-actions
کلاس متصل به یک عنصر div wrapper. ما می خواهیم این div را هدف قرار دهیم و یک دیوی جدید درج کنیم div
عنصر بین دکمه های فلش چپ و راست. از آن به عنوان مکانی برای تولید دکمه های صفحه بندی حباب سفارشی استفاده می شود.
ngAfterViewInit(): void {
this.styleDefaultPagination();
this.createBubbleDivRef();
}
private createBubbleDivRef(): void {
const actionContainer = this.elementRef.nativeElement.querySelector(
'div.mat-mdc-paginator-range-actions'
);
const nextButtonDefault = this.elementRef.nativeElement.querySelector(
'button.mat-mdc-paginator-navigation-next'
);
// create a HTML element where all bubbles will be rendered
this.bubbleContainerRef = this.ren.createElement('div') as HTMLElement;
this.ren.addClass(this.bubbleContainerRef, 'g-bubble-container');
// render element before the 'next button' is displayed
this.ren.insertBefore(
actionContainer,
this.bubbleContainerRef,
nextButtonDefault
);
}
ما ارجاع به عنصر div wrapper را توسط actionContainer
، با این حال ما نیز به آن نیاز داریم nextButtonDefault
به منظور اتصال صحیح ما div
عنصر در DOM، جایی که دکمه ها ارائه می شوند. ما همچنین این مرجع را در آن ذخیره می کنیم bubbleContainerRef
.
با استفاده از this.ren.insertBefore()
را وصل می کنیم div
عنصر داخل mat-mdc-paginator-range-actions
کلاس، قرار دادن div
عنصر قبل از دکمه بعدی صفحه بندی.
اگر از ما استفاده می شد this.ren.appendChild(actionContainer, this.bubbleContainerRef);
نتیجه نهایی دکمه های بعد از پیمایش پیکان ارائه می شود.
مرحله 4. رندر دکمه ها به DOM
در مرحله چهارم می خواهیم دکمه اول و آخر را رندر کنیم و بین آنها و بقیه دکمه ها نقطه اضافه کنیم. حتی اگر همه چیز را به این مثال ارائه کنیم، آنچه را که در ابتدا هر عنصر تنظیم شده است، مشاهده خواهید کرد display: none
، که در مرحله بعدی فقط موارد ضروری را آشکار می کنیم.
export class BubblePaginationDirective implements AfterViewInit {
/**
* how many elements are in the table
*/
@Input() appCustomLength: number = 0;
ngAfterViewInit(): void {
this.styleDefaultPagination();
this.createBubbleDivRef();
this.buildButtons();
}
// .... previous code
/**
* end result: (1) .... (4) (5) (6) ... (25)
*/
private buildButtons(): void {
const neededButtons = Math.ceil(
this.appCustomLength / this.matPag.pageSize
);
// if there is only one page, do not render buttons
if (neededButtons === 1) {
this.ren.setStyle(this.elementRef.nativeElement, 'display', 'none');
return;
}
// create first button
this.buttonsRef = [this.createButton(0)];
// add dots (....) to UI
this.dotsStartRef = this.createDotsElement();
// create all buttons needed for navigation (except the first & last one)
for (let index = 1; index < neededButtons - 1; index++) {
this.buttonsRef = [...this.buttonsRef, this.createButton(index)];
}
// add dots (....) to UI
this.dotsEndRef = this.createDotsElement();
// create last button to UI after the dots (....)
this.buttonsRef = [
...this.buttonsRef,
this.createButton(neededButtons - 1),
];
}
/**
* create button HTML element
*/
private createButton(i: number): HTMLElement {
const bubbleButton = this.ren.createElement('div');
const text = this.ren.createText(String(i + 1));
// add class & text
this.ren.addClass(bubbleButton, 'g-bubble');
this.ren.setStyle(bubbleButton, 'margin-right', '8px');
this.ren.appendChild(bubbleButton, text);
// react on click
this.ren.listen(bubbleButton, 'click', () => {
this.switchPage(i);
});
// render on UI
this.ren.appendChild(this.bubbleContainerRef, bubbleButton);
// set style to hidden by default
this.ren.setStyle(bubbleButton, 'display', 'none');
return bubbleButton;
}
/**
* Helper function to switch page
*/
private switchPage(i: number): void {
const previousPageIndex = this.matPag.pageIndex;
this.matPag.pageIndex = i;
this.matPag['_emitPageEvent'](previousPageIndex);
}
بیایید مرور کنیم که چه اتفاقی در حال رخ دادن است.
تابع buildButtons()
:
- محاسبه تعداد دکمه های صفحه بندی برای رندر کردن توسط
neededButtons
چون مجموع عناصر جدول است (appCustomLength
) تقسیم بر اندازه صفحه بندی. - اگر عناصر موجود در جدول کمتر از اندازه صفحه بندی باشند، چیزی را ارائه نمی کنیم
- اگر موارد بیشتری در جدول وجود دارد (
neededButtons
> 1) ما:- اولین دکمه را رندر کنید
- رندر نقطه ها
- دکمه های باقیمانده به جز آخرین مورد را رندر کنید
- رندر نقطه ها
- آخرین دکمه را رندر کنید
- دکمه ها را ذخیره کنید
createButton()
بهbuttonsRef
آرایه، زیرا بعداً مورد نیاز خواهند بود
تابع createButton()
:
- یک شاخص دریافت می کند، آن را یک افزایش می دهد و این مقادیر را به عنوان یک مقدار متنی به دکمه با چند کلاس CSS اضافی اضافه می کند.
- هر دکمه از DOM مخفی است، تنظیم شده توسط
this.ren.setStyle(bubbleButton, 'display', 'none');
. در مرحله بعد، برخی از آنها را در حین حرکت کاربر با کلیک روی آنها نمایش/پنهان می کنیم. - با پیوست کردن شنونده رویداد کلیک روی هر دکمه
this.ren.listen(bubbleButton, 'click' ...)
، تابع helper را می نامیمswitchPage()
برای تغییر صفحه فعلی
از مثال بالا createDotsElement()
وجود ندارد، زیرا یک تابع رندر مشابه است createButton()
و همچنین تعدادی کلاس CSS مانند g-bubble
برای یک ظاهر طراحی شده
یادداشت جانبی: در switchPage()
من لزوما مطمئن نیستم که چرا با _emitPageEvent
نیاز به انتشار دارد previousPageIndex
. من نتوانستم پاسخی برای آن پیدا کنم، اما کار می کند.
مرحله 5. به کلیک های ناوبری کاربر گوش دهید
به عنوان آخرین مرحله، میخواهیم منطقی را پیادهسازی کنیم که به کلیک کاربر روی دکمههای حباب گوش میدهد و شاخص فعال را با یک استایل تغییر میدهد.
در این لحظه همه دکمه ها مخفی هستند و مرجع آنها در آن نگه داشته می شود buttonsRef
. ما می خواهیم فقط 2-3 دکمه اول را نمایش دهیم و همانطور که کاربر به هر جهت حرکت می کند، می خواهیم 2 دکمه اضافی به راست و چپ و همچنین اگر کاربر در وسط پیمایش باشد، نقاط بین اولین و آخرین دکمه را نمایش دهیم.
export class BubblePaginationDirective implements AfterViewInit {
private buttonsRef: HTMLElement[] = [];
// .... previous code
ngAfterViewInit(): void {
this.styleDefaultPagination();
this.createBubbleDivRef();
this.buildButtons();
this.matPag.page
.pipe(
map((e) => [e.previousPageIndex ?? 0, e.pageIndex]),
startWith([0, 0])
)
.subscribe(([prev, curr]) => {
this.changeActiveButtonStyles(prev, curr);
});
}
// .... previous code
private changeActiveButtonStyles(previousIndex: number, newIndex: number) {
const previouslyActive = this.buttonsRef[previousIndex];
const currentActive = this.buttonsRef[newIndex];
// remove active style from previously active button
this.ren.removeClass(previouslyActive, 'g-bubble__active');
// add active style to new active button
this.ren.addClass(currentActive, 'g-bubble__active');
// hide all buttons
this.buttonsRef.forEach((button) =>
this.ren.setStyle(button, 'display', 'none')
);
// show 2 previous buttons and 2 next buttons
const renderElements = 2;
const endDots = newIndex < this.buttonsRef.length - renderElements - 1;
const startDots = newIndex - renderElements > 0;
const firstButton = this.buttonsRef[0];
const lastButton = this.buttonsRef[this.buttonsRef.length - 1];
// last bubble and dots
this.ren.setStyle(this.dotsEndRef, 'display', endDots ? 'block' : 'none');
this.ren.setStyle(lastButton, 'display', endDots ? 'flex' : 'none');
// first bubble and dots
this.ren.setStyle(
this.dotsStartRef,
'display',
startDots ? 'block' : 'none'
);
this.ren.setStyle(firstButton, 'display', startDots ? 'flex' : 'none');
// resolve starting and ending index to show buttons
const startingIndex = startDots ? newIndex - renderElements : 0;
const endingIndex = endDots
? newIndex + renderElements
: this.buttonsRef.length - 1;
// display starting buttons
for (let i = startingIndex; i <= endingIndex; i++) {
const button = this.buttonsRef[i];
this.ren.setStyle(button, 'display', 'flex');
}
}
ترسناک به نظر میرسه درسته 😓 نیازی به نگرانی نیست بیایید هر مرحله فکر کنیم که در این عملکرد چه اتفاقی می افتد، به شما کمک می کند تا این مزخرفات را درک کنید تا بتوانید بعداً آن را تغییر دهید.
ابتدا می خواهیم مشترک شویم [this.matPag.page](http://this.matPag.page)
قابل مشاهده و دریافت نمایه صفحه بندی کلیک شده قبلی و جدید. قابل مشاهده با هر کلیک دکمه حباب فعال می شود، زیرا در مرحله قبل ما پیوست می کنیم switchPage()
عملکرد به هر حباب
که در changeActiveButtonStyles()
موارد زیر اتفاق می افتد:
- ما عوض می کنیم
g-bubble__active
از حباب فعال قبلی به حباب جدید، که دکمه فعال فعلی را با نمایه های جدید ارائه شده برای عملکرد برجسته می کند. - به طور پیش فرض همه دکمه ها را پنهان کنید (
display: none
) - تعیین کنید که آیا نقطه های پایانی و آخرین دکمه نشان داده شود
endDots
، اگرnewIndex
آخرین 2 دکمه نیست - تعیین کنید که آیا نقطه های شروع را در ابتدا با اولین دکمه نشان دهید یا خیر
startDots
، اگرnewIndex
بیش از 2 - محاسبه شاخص برای نمایش دکمه ها از
buttonsRef
، برای نمایش 2 دکمه قبلی (startingIndex
) و 2 دکمه بعدی (endingIndex
) - مقدار نمایش را برای دکمه هایی که باید قابل مشاهده باشند تغییر دهید
خلاصه
این پست وبلاگ نحوه تغییر استایل مولفه MatPaginator Angular Material را برای ایجاد یک UI صفحه بندی سفارشی توضیح می دهد. این پست از طریق ایجاد یک دستورالعمل سفارشی، اصلاح طرح پیشفرض، قرار دادن یک div بین دکمههای ناوبری، رندر کردن دکمهها به DOM و گوش دادن به کلیکهای ناوبری کاربر میگذرد.
نتیجه نهایی یک رابط کاربری صفحهبندی سفارشی با دکمههای حبابدار و نقطههایی بین اولین و آخرین دکمه است. کل کد منبع این مثال در StackBlitz موجود است.
امیدوارم پست را دوست داشته باشید، به آن دل ببندید و با من در ارتباط باشید:
dev.to | لینکدین | وب سایت شخصی | Github