サンプルコードで身につける: ルートパラメータ — URLから値を受け取る
解説で学んだ概念を、5つの具体例で確認します。素朴な例から実務寄りの例へと進みます。
1URLのIDをそのまま表示する
ルートパラメータの最小例で、'articles/:id' の :id 部分を ActivatedRoute 経由で取り出します。/articles/3 にアクセスすれば '3' が表示される、というURLと画面の対応をまず体感する例です。paramMap.get に渡す名前は、ルート定義のコロンの後ろと一致させます。
import { Component, inject } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
// ルート定義側: { path: 'articles/:id', component: ArticleDetailComponent }
@Component({
selector: 'app-article-detail',
template: '<p>記事ID: {{ articleId }}</p>',
})
export class ArticleDetailComponent {
private route = inject(ActivatedRoute);
// /articles/3 なら '3' が入る
articleId = this.route.snapshot.paramMap.get('id');
}
2文字列のIDを数値に変換して使う
paramMap から取れる値は常に文字列なので、数値のIDと比較するときは Number() で変換します。変換したIDで配列から1件を探し、見つからない場合の表示も用意する例です。「URLの値は信用せず、見つからないケースを必ず考える」という実務の心得が入っています。
import { Component, inject } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
const PLANS = [
{ id: 1, planName: 'フリー' },
{ id: 2, planName: 'プロ' },
];
@Component({
selector: 'app-plan-detail',
// 見つからないケースの表示も忘れずに用意する
template:
'@if (plan) { <p>{{ plan.planName }}プラン</p> }' +
' @else { <p>プランが見つかりません</p> }',
})
export class PlanDetailComponent {
private route = inject(ActivatedRoute);
// 取れる値は文字列なので Number() で数値にしてから使う
planId = Number(this.route.snapshot.paramMap.get('id'));
plan = PLANS.find((p) => p.id === this.planId);
}
3複数のパラメータを受け取る
pathにはコロン付きの名前を複数書けて、それぞれを paramMap.get で個別に取り出せます。「店舗の中のメニュー」のような階層を持つデータでは、URLにも親子のIDを並べるのが自然な設計です。REST APIのURL設計とそのまま対応するので、実務で頻繁に登場します。
import { Component, inject } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
// ルート定義側: { path: 'shops/:shopId/menus/:menuId', ... }
@Component({
selector: 'app-menu-detail',
template: '<p>店舗 {{ shopId }} のメニュー {{ menuId }}</p>',
})
export class MenuDetailComponent {
private route = inject(ActivatedRoute);
// /shops/3/menus/10 なら shopId は '3'、menuId は '10'
shopId = this.route.snapshot.paramMap.get('shopId');
menuId = this.route.snapshot.paramMap.get('menuId');
}
4クエリパラメータで検索条件を受け取る
/search?q=apple&page=2 のような ? 以降の値は、paramMap ではなく queryParamMap で取り出します。ルート定義に書かなくても受け取れるのが、パスのパラメータとの違いです。検索条件やページ番号をURLに持たせると、検索結果のページをそのまま共有・ブックマークできるようになります。
import { Component, inject } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
@Component({
selector: 'app-search-result',
template: '<p>「{{ searchWord }}」のページ {{ pageNumber }} を表示中</p>',
})
export class SearchResultComponent {
private route = inject(ActivatedRoute);
// /search?q=apple&page=2 のような ? 以降は queryParamMap で取る
searchWord = this.route.snapshot.queryParamMap.get('q') ?? '';
pageNumber = Number(this.route.snapshot.queryParamMap.get('page') ?? '1');
}
5IDでAPIから詳細データを取得する
「一覧で商品をクリック → /products/7 へ遷移 → URLのIDでAPIに問い合わせて詳細を表示」という、詳細ページの実務フローを通しで示す例です。データが届くまでは読み込み中の表示を出し、届いたらシグナル経由で画面が切り替わります。ルートパラメータ・HttpClient・シグナルが1つの画面で協調する集大成です。
import { Component, OnInit, inject, signal } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { HttpClient } from '@angular/common/http';
type Product = { id: number; productName: string; price: number };
@Component({
selector: 'app-product-detail',
template:
'@if (product()) {' +
' <h1>{{ product()!.productName }}</h1><p>{{ product()!.price }} 円</p>' +
'} @else {' +
' <p>読み込み中...</p>' +
'}',
})
export class ProductDetailComponent implements OnInit {
private route = inject(ActivatedRoute);
private http = inject(HttpClient);
product = signal<Product | null>(null);
// ngOnInit はコンポーネント初期化時に1回だけ呼ばれるフック
ngOnInit() {
// URLのIDをそのままAPIへの問い合わせに使う
const productId = this.route.snapshot.paramMap.get('id');
this.http
.get<Product>('/api/products/' + productId)
.subscribe((data) => this.product.set(data));
}
}