カテゴリー
【Angular基礎講座】動的/静的ウェブサイト内のページネイション作成 〜 パスパラメータ/クエリパラメータ付きのURLアドレスからルーティングする
※ 当ページには【広告/PR】を含む場合があります。
2020/09/29
2022/08/10
ウェブページのコンテンツが増えてくると、一つのページのコンテンツの全てを載せていてはとても大きなファイルになるため、リストでコンテンツを小分けしたページ分割を検討していく必要があります。
おそらくそのような場合、
/home/hoge/piyo
/home?param1=hoge¶m2=piyo
今回はAngularでの動的・静的ウェブページのURLパラメータによるルーティングをどう実現するのか検討してみます。
TL;DR
Angularで動的なウェブページをページング処理する際には、クエリパラメータを用いたルーティングを用いた方が実装しやすいです。
その際には
ActivatedRoute
queryParams
// `/home?param1=hoge¶m2=piyo`にアクセスしたときのparam1とparam2を取得
constructor(private route: ActivatedRoute) {
const param1: string = this.route.snapshot.queryParamMap.get('param1'); // hoge
const param2: string = this.route.snapshot.queryParamMap.get('param2'); // piyo
}
静的なウェブページは、各階層の
index.html
そこでパス変数(パスに
:
この場合、
ActivatedRoute
snapshot
params
// `/home/:param1/:param2`とパスパラメータを定義して、
// 例えば/home/hoge/piyoを取得
constructor(private route: ActivatedRoute) {
const param1: string = this.route.snapshot.paramMap.get('param1'); // hoge
const param2: string = this.route.snapshot.paramMap.get('param2'); // piyo
}
動的なサイトのページング処理
Angularを使った動的なページネーションは主に、リクエストしたurl文字例のサフィックスからパラメータを読み取り、内部のjsスクリプトがコンポーネントを適切にhtmlをレンダリングして描画する処理を行っています。

レンダリングの作業をクライアント側のブラウザが行うか、サーバーサイドからレンダリングして返されるかの違いはあります。
基本的には同一のルート(ここでは
/home
app-routingモジュール
まず最初にプロジェクトの
app-routing.module.ts
この記事では割愛しますが、もしまだプロジェクトにapp-routing.module.tsを実装していない場合には、angular公式ガイドの
今回は以下のようなルーティング構造でページング処理を行います。
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { HomeComponent } from './home/home.component';
import { PageNotFoundComponent } from './page-not-found/page-not-found.component';
const routes: Routes = [
{ path: 'error', component: PageNotFoundComponent },
{ path: 'home', component: HomeComponent },
{ path: '', redirectTo: '/home', pathMatch: 'full' },
{ path: '**', redirectTo: '/error', pathMatch: 'full' }
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
動的なサイトのページング処理
今回はコンテンツを10件ごとに小分けして、1つのページで区切るようにしようと思います。
以下は、例えば基点となるHomeComponent(
/home
/home?page=1
/home?page=2
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
@Component({
selector: 'app-root',
template: `
<div>
<ul>
<ng-container *ngFor="let article of articleList | slice:startPos:startPos+listLength">
<li>
<a href="{{article.url}}">{{article.title}}</a>
<p>{{article.subtitle}}</p>
</li>
</ng-container>
</ul>
<ng-container *ngIf="articleList.length === 0">
<div>
<p>このカテゴリーにはまだ記事が有りません。</p>
</div>
</ng-container>
</div>
`,
styleUrls: ['./home.component.css']
})
export class HomeComponent implements OnInit {
startPos: number = 0; // リストの最初の要素番号
listLength: number = 10; // リストへの最大表示数
articleList: any[] = [
{
url: '/blog/1',
title: 'Hogeの日記',
subtitle: '今日はお肉の日でした。',
},
{
url: '/blog/2',
title: 'Piyoの日記',
subtitle: '今日はおさかなの日でした。',
},
//...中略
];
constructor(
private route: ActivatedRoute
) { }
ngOnInit() {
// リスト表示に必要なページ数の更新
const totalPages: number = Math.floor(this.articleList.length / this.listLength) + 1;
// リクエストされたルートのクエリパラメータを取得
let pageId: number = parseInt(this.route.snapshot.queryParamMap.get('page'), 10); // 1,2,3...
if (!pageId || pageId <= 0) {
// 1より小さい値はとらない
pageId = 1;
} else if (pageId <= totalPages) {
// そのページの描画開始位置を算出
this.startPos = this.listLength * (pageId - 1);
} else {
// 最大のページ数を越えないようにする
this.startPos = this.listLength * (totalPages - 1);
}
}
}
html部分を見ていただくと、
ngFor
slice
ちなみに
slice
ページ毎に、コンテンツリストの最初の位置と描画長までを取り出しています。
さて今回の実装では、
this.route.snapshot.queryParamMap.get('page')
page=*
何ページ目かを読み取った後にDOMを生成しなければならないので、クエリパラメータの読み取りは
constructor
ngOnInit
クエリパラメータを用いたページの遷移
ページをクエリパラメータから生成・表示させるロジックは上記の通りですが、プログラム側からアクセスさせることもできます。
Angularでクエリパラメータを使ったナビゲーションの方法として、Componentのtsソース側に実装する場合には下記のようになります。
// /home?page=1
this.router.navigate(
['/home'],
{
queryParams: {
page: 1
}
}
);
htmlコード内で利用する場合には、
routerLink
<!-- /home?page=1 -->
<a [routerLink]="['/home']" [queryParams]="{ page: 1 }"></a>
静的なサイトのページング処理
先ほどの動的なページネーションとは違い、静的なウェブサイトでは、サーバー側に予めレンダリングしておいた
index.html
このときリクエストされたurl文字列に応じた
index.html

動的なページネーションではユーザーがurlをリクエストしてからその場限りのページがレンダリングされることで動作しています。
なので、ユーザーが
/home
/home/index.html
/home/1
/home/1/index.html
angularにおける静的なサイトのページング処理の方法と比べて、上記までの動的なページングよりも複雑です。
プロジェクトをビルドするときに、angularのコンパイラにどのルートをプリレンダリングしておくのか明示に教えてあげるという一手間が必要になります。
以下では簡単にプリレンダリングしたhtmlでのページネーションの実装手順を解説します。
app-routingモジュールの修正
angularのルーティングにおいて、パスパラメータを使うためには、
:
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { HomeComponent } from './home/home.component';
import { PageNotFoundComponent } from './page-not-found/page-not-found.component';
const routes: Routes = [
{ path: 'error', component: PageNotFoundComponent },
{ path: 'home', component: HomeComponent },
{ path: 'home/:page', component: HomeComponent }, // 👈追加
{ path: '', redirectTo: '/home', pathMatch: 'full' },
{ path: '**', redirectTo: '/error', pathMatch: 'full' }
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
ここでは、
home/
page
サーバーサイドレンダリング(SSR)
通常angularのSSRには、
1. ビルド時のコマンドのオプション--routesに指定のパスパラメータを追加する
2. angular.jsonのprerenderのroutesフィールドにパスパラメータを追加する
3. ビルドコマンドのオプション--routesFileを使って、
dynamic-routes.txtを指定し、このテキストにパスを追加する
4. angular.jsonのprerenderのroutesFileフィールドに、
dynamic-routes.txtのパスを追加し、ビルド時に読み込ませる
の4つの方法のどれかで、静的なウェブサイトのページング処理を行うことが可能です。
とはいえこのやり方ですと、angularのビルドコマンドを直接利用している力技になります。
そこで、
angular-prerender
コマンドオプション
--parameter-values
index.html
今回の例では、
:page
$ npx angular-prerender --parameter-values '{":page":["1","2","3","4","5","6"]}'
/home/1
/home/2
/home/3
/home/4
/home/5
/home/6
もちろん
:page
全てのビルド作業を自動化したい場合はシェルスクリプト等でCLIを使ったカスタマイズをする必要があります。
静的なサイトのページング処理
最後にパスパラメータを適切に処理できるように、先程の
HomeComponent
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
@Component({
selector: 'app-root',
template: `
<div>
<ul>
<ng-container *ngFor="let article of articleList | slice:startPos:startPos+listLength">
<li>
<a href="{{article.url}}">{{article.title}}</a>
<p>{{article.subtitle}}</p>
</li>
</ng-container>
</ul>
<ng-container *ngIf="articleList.length === 0">
<div>
<p>このカテゴリーにはまだ記事が有りません。</p>
</div>
</ng-container>
</div>
`,
styleUrls: ['./home.component.css']
})
export class HomeComponent implements OnInit {
startPos: number = 0; // リストの最初の要素番号
listLength: number = 10; // リストへの最大表示数
articleList: any[] = [
{
url: '/blog/1',
title: 'Hogeの日記',
subtitle: '今日はお肉の日でした。',
},
{
url: '/blog/2',
title: 'Piyoの日記',
subtitle: '今日はおさかなの日でした。',
},
//...中略
];
constructor(
private route: ActivatedRoute
) { }
ngOnInit() {
// リスト表示に必要なページ数の更新
const totalPages: number = Math.floor(this.articleList.length / this.listLength) + 1;
// リクエストされたルートのパスパラメータを取得
let pageId: number = parseInt(this.route.snapshot.paramMap.get('page'), 10); // 1,2,3...
if (!pageId || pageId <= 0) {
// 1より小さい値はとらない
pageId = 1;
} else if (pageId <= totalPages) {
// そのページの描画開始位置を算出
this.startPos = this.listLength * (pageId - 1);
} else {
// 最大のページ数を越えないようにする
this.startPos = this.listLength * (totalPages - 1);
}
}
}
見ていただくと分かる通り、
this.route.snapshot.paramMap
まとめ
今回はAngularを用いた静的・動的なウェブページのページング処理のための簡単な概論として記事にしてみました。
Angular Universalの登場から直近まででSSR・プリレンダリングの技術は大分使いものになって来た印象です。
ただ、まだまだ不安定な要素も多いため今後もより使いやすいものに開発が進んでいくと思われます。
参考サイト
記事を書いた人
ナンデモ系エンジニア
主にAngularでフロントエンド開発することが多いです。 開発環境はLinuxメインで進めているので、シェルコマンドも多用しております。 コツコツとプログラミングするのが好きな人間です。
カテゴリー