Dev Study
← 解説「クエリパラメータ — 検索条件をURLに載せる」に戻る

サンプルコードで身につける: クエリパラメータ — 検索条件をURLに載せる

解説で学んだ概念を、5つの具体例で確認します。素朴な例から実務寄りの例へと進みます。

1queryParamMap でクエリを読み取る

クエリパラメータ読み取りの最小例です。ルートパラメータの paramMap と対になる queryParamMap から、? 以降の値を名前で取り出します。/items?keyword=りんご のようなURLで keyword の値が手に入ります。無ければ null になる点に注意してください。

import { Component, inject } from '@angular/core';
import { ActivatedRoute } from '@angular/router';

@Component({
  selector: 'app-item-search',
  template: '<p>検索ワード: {{ keyword }}</p>',
})
export class ItemSearch {
  private route = inject(ActivatedRoute);

  // /items?keyword=りんご なら 'りんご' が入る(無ければ null)
  keyword = this.route.snapshot.queryParamMap.get('keyword');
}

2[queryParams] 付きのリンクを作る

リンク側からクエリを付ける定番の書き方です。routerLink にパスを、[queryParams] にオブジェクトを渡すと、?sort=price&order=asc のようなURLが組み立てられます。並び替えボタンをリンクとして作れるため、右クリックで新しいタブに開いても同じ並び順が再現されます。

import { Component } from '@angular/core';
import { RouterLink } from '@angular/router';

@Component({
  selector: 'app-sort-menu',
  imports: [RouterLink],
  // /items?sort=price や /items?sort=new へのリンクになる
  template:
    '<nav>' +
    '  <a [routerLink]="[\'/items\']" [queryParams]="{ sort: \'new\' }">新着順</a>' +
    '  <a [routerLink]="[\'/items\']" [queryParams]="{ sort: \'price\' }">価格順</a>' +
    '</nav>',
})
export class SortMenu {}

3ページ番号を数値に変換して初期値も持つ

クエリの値は常に文字列で、無いときは null です。Number() での変換と ?? での初期値をセットにしておくと、?page= が無い初回アクセスでも1ページ目として扱えます。一覧ページのページネーションでそのまま使える形です。

import { Component, inject } from '@angular/core';
import { ActivatedRoute } from '@angular/router';

@Component({
  selector: 'app-item-list',
  template: '<p>{{ page }}ページ目(1ページ{{ perPage }}件)</p>',
})
export class ItemList {
  private route = inject(ActivatedRoute);

  // 文字列を数値に変換し、無ければ初期値を使う
  page = Number(this.route.snapshot.queryParamMap.get('page') ?? '1');
  perPage = Number(
    this.route.snapshot.queryParamMap.get('perPage') ?? '20'
  );
}

4navigate でクエリを付けて遷移する

検索フォームの送信後など、コード側からクエリ付きで遷移する例です。navigate の第2引数の queryParams にオブジェクトを渡します。「入力された条件をURLに載せて一覧へ飛ぶ」という流れにすると、検索結果ページ自体が共有可能なURLになります。

import { Component, inject, signal } from '@angular/core';
import { Router } from '@angular/router';

@Component({
  selector: 'app-search-form',
  template:
    '<input #box (input)="keyword.set(box.value)" />' +
    '<button (click)="search()">検索</button>',
})
export class SearchForm {
  private router = inject(Router);
  keyword = signal('');

  search() {
    // /items?keyword=入力値 へ遷移する
    this.router.navigate(['/items'], {
      queryParams: { keyword: this.keyword() },
    });
  }
}

5クエリの変化を購読して絞り込みに反映する

同じ一覧ページ内で ?sort= だけが変わる遷移では、コンポーネントは作り直されないため snapshot だと古い値のままです。queryParamMap を subscribe すると、クエリが変わるたびにコールバックが呼ばれ、表示を追従させられます。snapshot との使い分けがクエリ最大のつまずきポイントです。

import { Component, inject, signal } from '@angular/core';
import { ActivatedRoute } from '@angular/router';

const ITEMS = [
  { name: 'りんご', price: 150 },
  { name: 'メロン', price: 800 },
];

@Component({
  selector: 'app-item-list',
  template:
    '@for (item of items(); track item.name) {' +
    ' <p>{{ item.name }} {{ item.price }}円</p> }',
})
export class ItemList {
  private route = inject(ActivatedRoute);
  items = signal(ITEMS);

  ngOnInit() {
    // 同一ページ内でクエリが変わっても呼ばれる
    this.route.queryParamMap.subscribe((params) => {
      const sort = params.get('sort') ?? 'name';
      const sorted = [...ITEMS].sort((a, b) =>
        sort === 'price' ? a.price - b.price : a.name.localeCompare(b.name)
      );
      this.items.set(sorted);
    });
  }
}