Dev Study
← 解説「ルートパラメータ — URLから値を受け取る」に戻る

サンプルコードで身につける: ルートパラメータ — 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));
  }
}