Veri setleri büyüdükçe, tüm verileri bir seferde yüklemek yerine sayfa sayfa yüklemek, yani “Server Side Pagination” yani “Sunucu Taraflı Sayfalama” kullanmak, performansı ve kullanıcı deneyimini optimize etmek açısından önemli bir rol oynar.
Server Side Pagination Nedir?
Bu yöntemde, sayfalama işlemi sunucu tarafında gerçekleştirilir ve sadece mevcut sayfa için gerekli olan veriler istemciye gönderilir. Bu, kullanıcının sadece ihtiyaç duyduğu verilere odaklanmasını sağlar ve sayfa yükleme sürelerini önemli ölçüde azaltır.
Neden Server Side Pagination Kullanmalıyız?
Performans:
- Büyük veri setlerini bir seferde yüklemek, hem sunucu hem de istemci tarafında performans sorunlarına yol açabilir.
- Server Side Pagination, yalnızca görüntülenen veri miktarını minimize ederek daha hızlı sayfa yüklemeleri sağlar.
Veri Güncelliği:
- Server Side Pagination, veritabanındaki değişikliklere anında tepki gösterme yeteneği ile güncel verilerin kullanıcıya sunulmasını sağlar.
Veri Güvenliği:
- Özellikle hassas verilerle çalışıyorsanız, tüm verileri istemci tarayıcısına göndermek yerine sadece gerekli olan verileri almak, güvenlik açısından daha sağlam bir yaklaşımdır.
Angular ile Server Side Pagination Nasıl Gerçekleştirilir?
Angular, güçlü bir web uygulama çerçevesi olup, Server Side Pagination uygulamak için bir dizi araç ve yöntem sunar. Angular’ın sunduğu bileşenler, servisler ve HTTP istekleri üzerinden Server Side Pagination’ı etkili bir şekilde uygulamayı göreceğiz.
Adım adım Server Side Pagination’ı uygulamaya başlayacağız. İlk adım olarak, Angular projesinin temelini oluşturmak için gereken adımları göreceğiz.
Öncelikle server side pagination için API hizmetinin bize sunması gereken bazı özellikler olmalıdır.
Temelde bize lazım olan özelliklerin şunlar olduklarını söyleyebiliriz.
- data: Sayfanın içeriğini temsil eder.
- totalCount: Toplam öğe sayısını belirtir.
- page: Şu anki sayfa numarasını belirtir.
- pageSize: Her sayfada kaç öğe olması gerektiğini belirtir.
Bu özellikler tarafımıza sunan bir API servisini kullanarak başarılı bir şekilde server side pagination yapabiliyoruz.
Başlangıç olarak bir Angular projesi üretelim.
npm install -g @angular/cli
ng new angular-pagination-demo
cd angular-pagination-demo
ng serve
Bu komut, geliştirme sunucusunu başlatacak ve varsayılan olarak http://localhost:4200/
adresinde çalışan bir uygulama sunacaktır.
Projemizde sınıflarımızı oluşturmadan önce API hizmetine bir bakalım.
Burada konuyu çok dağıtmamak adına hali hazırda bize dummy data sunan bir api adresi kullanacağız.
Aşağıdaki sitede işimize yarayabilecek endpointler bulunmakta.
Görüldüğü üzere LIST USERS’a bir GET isteği yaptığımızda bize lazım olan özelliklerle birlikte bir dönüş yapılmakta.
Bu adrese bir GET isteği yapabilmek ve bu isteği yaparken hangi modeli kullanacağımıza geçebiliriz.
Angular projemize geri dönelim ve bu isteği karşılayacak bir interface üretelim.
export interface User {
id: number;
first_name: string;
last_name: string;
email: string;
avatar: string;
}
export interface PagedUserData{
data: User[];
page: number;
per_page: number;
total: number;
total_pages: number;
}
export interface Project {
name: string;
id: number;
}
Endpointin bize dönüş değerlerine baktığımızda yukarıdaki modeller buna uygun görünmekte. Ana model olarak PagedUserData kullanacağız.
Şimdi API hizmetine ulaşacak olan servisimizi yazalım.
ng g s user
komutu ile projemizde yeni bir servis sınıfı tanımlayabiliriz.
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { PagedUserData } from './app/interfaces/user.interface';
@Injectable({
providedIn: 'root'
})
export class UserService {
constructor(private http: HttpClient) { }
getUserList(pageNumber: Number, pageSize: Number): Observable<PagedUserData> {
const url = `https://reqres.in/api/users?page=${pageNumber}&per_page=${pageSize}`;
return this.http.get<PagedUserData>(url);
}
}
Sınıfımızı detaylı bir şekilde inceleyelim.
Modül ve Kütüphaneler:
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { PagedUserData } from './app/interfaces/user.interface';
HttpClient
: Angular uygulamalarında HTTP isteklerini yönetmek için kullanılan bir modüldür.Injectable
: Bu dekoratör, servis sınıfının bir bağımlılık olarak enjekte edilebilir olduğunu belirtir.Observable
: RxJS kütüphanesinden gelir ve asenkron veri akışını temsil eder.PagedUserData
: Önceki cevapta oluşturduğunuz kullanıcı verilerini ve sayfalama bilgilerini içeren arayüz.
Servis Sınıfı Tanımlama:
@Injectable({
providedIn: 'root'
})
@Injectable
dekoratörü, servis sınıfının Angular enjeksiyon sistemi tarafından yönetileceğini belirtir. providedIn: 'root'
ifadesi, servisin uygulama genelinde paylaşılacağını gösterir.
Constructor ve HttpClient Enjeksiyonu:
constructor(private http: HttpClient) { }
HttpClient
sınıfı, servis sınıfının constructor'ında enjekte edilir. Bu, HTTP istekleri yapmak için kullanılacaktır.
getUserList Metodu:
getUserList(pageNumber: Number, pageSize: Number): Observable<PagedUserData> {
const url = `https://reqres.in/api/users?page=${pageNumber}&per_page=${pageSize}`;
return this.http.get<PagedUserData>(url);
}
getUserList
metodu, kullanıcı verilerini çekmek için HTTP GET isteği yapar.pageNumber
vepageSize
parametreleri, hangi sayfanın ve sayfadaki öğe sayısının alınacağını belirler.http.get<PagedUserData>(url)
: HTTP GET isteği yapar ve bu isteğin tipiniPagedUserData
olarak belirtir. Bu, RxJS tarafından sağlanan birObservable
döner.
Sıra bu servisimizi kullanacak olan user-list adındaki componentimizi oluşturmaya geldi.
ng g c user-list
Komutu ile yeni bir angular componenti oluşturuyoruz.
Bu işlemden hemen sonra app.component.html dosyasına ekleme yapabiliriz. Selector kısmına dikkat edin. Sizde app-user-list olabilir.
<user-list></user-list>
<router-outlet></router-outlet>
Projemizde Material bileşenlerini kullanacağız. Material kurulumu içinde küçük bir kod yazalım.
ng add @angular/material
user-list.component.ts
import { AfterViewInit, ChangeDetectorRef, Component, OnInit, ViewChild } from '@angular/core';
import { merge, Observable, of as observableOf } from 'rxjs';
import { catchError, map, startWith, switchMap } from 'rxjs/operators';
import { UserService } from 'src/userservice.service';
import { MatPaginator } from '@angular/material/paginator';
import { MatTableDataSource } from '@angular/material/table';
import { User, PagedUserData } from '../interfaces/user.interface';
@Component({
selector: 'user-list',
templateUrl: './user-list.component.html',
styleUrls: ['./user-list.component.css']
})
export class UserListComponent {
dataSource = new MatTableDataSource<User>();
@ViewChild('paginator') paginator: MatPaginator;
pageSizes = [3, 5, 7];
totalData: number = 7;
isLoading = false;
displayedColumns: string[] = [
'id',
'first_name',
'last_name',
'email',
'avatar',
];
UserData: User[];
constructor(private userService: UserService, private cdr: ChangeDetectorRef) { }
getUserListTableData$(pageNumber: number, pageSize: number): Observable<PagedUserData> {
return this.userService.getUserList(pageNumber, pageSize);
}
ngAfterViewInit() {
this.dataSource.paginator = this.paginator;
merge(this.paginator.page)
.pipe(
startWith({}),
switchMap(() => {
this.isLoading = true;
return this.getUserListTableData$(
this.paginator.pageIndex + 1,
this.paginator.pageSize
).pipe(catchError(() => observableOf(null)));
}),
map((userTableData) => {
if (userTableData == null) return [];
this.totalData = userTableData.total;
this.isLoading = false;
return userTableData.data;
})
)
.subscribe((userData) => {
this.UserData = userData;
this.dataSource = new MatTableDataSource(this.UserData);
this.cdr.detectChanges();
});
}
}
Karmaşıkmı geldi? :) Biraz bakalım. Öncelikle constructorda bir servis enjeksiyonu yaparak biraz önce oluşturduğumuz servisi inject ediyoruz.
Sonrasında getUserListTableData fonksiyonu ile servisimize ulaşacak ve önyüzden topladığımız verileri args olarak gönderebileceğiz.
ngAfterViewInit
metodu, bileşenin view (görünüm) bileşeninin oluşturulduktan ve görüntülendikten hemen sonra çalıştırılır.
Dolasıyla tablo işleyişini bu fonksiyonun içerisine yazabiliriz.
merge(this.paginator.page)
: MatPaginator
'daki sayfa değişikliklerini izleyen bir Observable oluşturur. Yani, sayfalandırıcıda bir değişiklik olduğunda bu değişiklikleri yakalayabiliriz.
pipe(...)
ve switchMap(() => {...})
: Bu kısımda, sayfa değiştikçe çalışacak olan işlemler tanımlanır.
switchMap
: Her sayfa değişikliğinde,getUserListTableData$
metodunu çağırır. Bu metot, servis aracılığıyla kullanıcı listesi verilerini getirir.catchError(() => observableOf(null))
: Eğer bir hata olursa, bu hatayı yakalar venull
ile birlikte bir Observable döner.
map((userTableData) => {...})
: getUserListTableData$
metodunun döndüğü veriyi işler.
if (userTableData == null) return [];
: Eğer veri yoksa, boş bir dizi döndürür.this.totalData = userTableData.total;
: Toplam veri sayısını günceller.this.isLoading = false;
: Yükleniyor durumunu kapatır.return userTableData.data;
: Kullanıcı verilerini döndürür.
.subscribe((userData) => {...})
: En sonunda, bu işlemleri dinlemek üzere bir subscribe
işlemi gerçekleştirilir.
this.UserData = userData;
: Kullanıcı verilerini günceller.this.dataSource = new MatTableDataSource(this.UserData);
:MatTableDataSource
'ı günceller.this.cdr.detectChanges();
:
Değişiklik dedektörünü çalıştırır, böylece Angular, component'in durumundaki değişiklikleri algılar ve günceller.
Bir sonraki adımda; bu komponent içerisinde kullanacağımız bileşenleri modül olarak eklememizde gerekmekte.
Modülleri isterseniz tek tek app.module sayfasını içerisine ekleyebilir istersenizde daha düzenli olması için ayrı bir modül sınıfı yazarak, ana modül sınıfınızda bu sınıfı gösterebilirsiniz.
mt-components.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MatTableModule } from '@angular/material/table';
import { MatInputModule } from '@angular/material/input';
import { MatSelectModule } from '@angular/material/select';
import { MatCardModule } from '@angular/material/card';
import { MatSortModule } from '@angular/material/sort';
import { MatPaginatorModule } from '@angular/material/paginator';
import { MatProgressBarModule } from '@angular/material/progress-bar';
@NgModule({
declarations: [],
imports: [
MatTableModule,
CommonModule,
MatInputModule,
MatSelectModule,
MatCardModule,
MatSortModule,
MatPaginatorModule,
MatProgressBarModule,
],
exports: [
MatTableModule,
MatInputModule,
MatSelectModule,
MatCardModule,
MatSortModule,
MatPaginatorModule,
MatProgressBarModule,
],
})
export class MaterialComponentsModule { }
Oluşturulan modül dosyasını ana modül dosyamıza referans edelim.
app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { HttpClientModule } from '@angular/common/http';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { UserListComponent } from './user-list/user-list.component';
import { MatTableModule } from '@angular/material/table';
import { MatPaginatorModule } from '@angular/material/paginator';
import { MaterialComponentsModule } from 'src/shared/metarial-components.module';
@NgModule({
declarations: [
AppComponent,
UserListComponent,
],
imports: [
BrowserModule,
AppRoutingModule,
BrowserAnimationsModule,
HttpClientModule,
MatTableModule,
MatPaginatorModule,
MaterialComponentsModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
imports dizisinin en alt satırında MaterialComponentsModule olarak referans ettik.
Artık Material Components’e ait çeşitli araçları kullanabileceğiz.
Material component gerekli değil. Çok daha farklı yapılar kullanarakta tablolar oluşturabilir ve yönetebilirsiniz. Material yapısıda gördüğüm kadarıyla popüler ve şık görünmekte.
Şimdi önyüz tasarımımızı yapalım.
user-list.component.ts
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8">
<ng-container [matColumnDef]="column" *ngFor="let column of displayedColumns">
<th mat-header-cell *matHeaderCellDef>{{ column }}</th>
<td mat-cell *matCellDef="let usr">{{ usr[column] }}</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let usrRow; columns: displayedColumns"></tr>
</table>
<mat-progress-bar mode="indeterminate" *ngIf="isLoading"></mat-progress-bar>
<mat-paginator #paginator [length]="totalData" [pageSizeOptions]="pageSizes" showFirstLastButtons></mat-paginator>
Şimdi önyüzün nasıl göründüğüne bir bakalım.
Proje repo
Makalemiz burada son buluyor. Okuduğunuz için teşekkür ederim. Bir sonraki makalede görüşmek üzere. Hoşçakalın..