パイプ (Pipe) とは何か?
Angularにおけるパイプ (Pipe) は、テンプレート内で表示するデータを「見た目」や「形式」の観点から変換・加工する仕組みです。このページでは、初心者向けのイメージと、仕組みの本質的な部分を段階的に解説していきます。
パイプ (Pipe) とは何か?
パイプの目的・役割
- データ変換 (Formatting / Transforming)
テンプレート (HTML) に表示する「直前」で、コンポーネントから受け取った生データを、ユーザーが見やすい・使いやすい形に整形する。
例)日付の表示形式、数値の3桁区切り、文字列の大文字化など。 - テンプレートの可読性向上
もしパイプがない場合、コンポーネントやテンプレート内にフォーマット用の複雑な処理が散らばるおそれがある。パイプを使うことで、- ビジネスロジック (コンポーネント) と表示用のフォーマット処理 (ビュー) を分離しやすい
- テンプレートは「必要な変換をパイプでつなぐ」だけになる
ため可読性が向上する。
- Angular が提供するビルトイン機能との連携
代表的なビルトインパイプ (DatePipe
,NumberPipe
,CurrencyPipe
,PercentPipe
,UpperCasePipe
など) を利用することで、面倒なデータ変換の多くを少ないコードで実現できる。
パイプを使うには
- “|” (パイプ記号) を使う
テンプレート内で{{ 変数 | pipe名 }}
のように使います。複数のパイプをつなげて使うこともできます。
<!-- 例: 価格を3桁区切りにフォーマットし、通貨も付けて表示する --> {{ price | number:'1.0-0' | currency:'JPY' }}
初心者がつかむべき「イメージ」と簡単な利用シーン
単純なビルトインパイプの利用例
<!-- コンポーネントのプロパティ --> export class ExampleComponent { today = new Date(); // たとえば 2025-01-18 price = 1234.5; userName = 'angular user'; }
<!-- テンプレート --> <!-- ① 日付を表示 --> <p>Today: {{ today | date:'yyyy/MM/dd' }}</p> <!-- 出力例: Today: 2025/01/18 --> <!-- ② 価格を3桁区切りにして、通貨表記に --> <p>Price: {{ price | currency:'USD':'symbol':'1.0-2' }}</p> <!-- 出力例: Price: $1,234.50 --> <!-- ③ 文字列をすべて大文字に --> <p>User: {{ userName | uppercase }}</p> <!-- 出力例: User: ANGULAR USER -->
- このように、
{{ 変数 | pipe名:オプション }}
の形で、テンプレート上で値を変換するのがパイプの基本的な使い方です。 - 利用シーン: ユーザーに見せたい情報があるとき、コンポーネント側のデータ構造や型はそのままで、最後の「表示」のタイミングだけパイプでスッキリ変換できる。
複数パイプの連続利用
1つの値に対して、パイプ記号 |
を複数つなげることも可能です。
<!-- 価格を3桁区切り→JPY表記→大文字に --> <p>{{ price | number:'1.0-0' | currency:'JPY':'symbol' | uppercase }}</p>
price
を整数表記にして (number:'1.0-0'
)、currency
で通貨(¥)を付与し (currency:'JPY'
)、uppercase
で最終的に大文字化したものが表示される。
データ変換が段階的に行われるため、一連のフォーマット処理をテンプレート上で可視化しやすいのが利点の1つです。
カスタムパイプの基本と概念
Angularで用意されているビルドインパイプでは要件を満たさない場合、自分でパイプを作成することもできます。
例)日付 +「何日前」「何時間前」といった表示形式にしたい、配列を特定の条件でフィルタリングしたい、など。
カスタムパイプの最小構成
// ex-rate.pipe.ts import { Pipe, PipeTransform } from '@angular/core'; @Pipe({ name: 'exRate' // テンプレート上で使うパイプ名 }) export class ExRatePipe implements PipeTransform { // transformメソッドが肝。引数は自由に定義できる(最初が「値」、それ以降がパラメータ) transform(value: number, rate: number): number { return value * rate; } }
@Pipe()
デコレーターで、パイプであることとパイプ名を宣言。PipeTransform
インターフェースのtransform
メソッドを実装する(必須)。- 変換したい処理を
transform
で書き、戻り値を返す。
テンプレートでの使用例
<!-- コンポーネントで amount = 100 として、1ドル=135円換算 で表示 --> <p>JPY: {{ amount | exRate:135 }}</p> <!-- 実行結果: 13500 -->
Angular のパイプ (Pipe) は PipeTransform
インターフェースを実装しなければなりません。その必須メソッドが transform
です。
- 役割: テンプレートから受け取った「変換元の値」と「パイプに与える引数 (オプション)」を受け取り、データ変換の結果を返却する。
- イメージ: 「生データ →
transform
→ 表示用データ」の流れをになう変換ロジック本体。
@Pipe({ name: 'exRate' }) export class ExRatePipe implements PipeTransform { transform(value: number, rate: number): number { return value * rate; } }
上記の例では transform(value, rate)
という形で「入力値とパラメータを受け取り、掛け算した結果」を返しています。テンプレートで {{ amount | exRate:135 }}
と書くと、
amount
がvalue
に相当135
がrate
に相当transform(100, 135)
→13500
が戻り値
…という流れで表示に反映されます。
Change Detection (変更検知) との関係
(1) Angular における変更検知
- Angular はコンポーネントのプロパティが変わったかどうかを監視 (Change Detection) し、変更があればテンプレートを再描画 (再評価) する仕組み。
- 純粋パイプ (Pure Pipe) は「入力値 (およびその参照) が変わったときのみ」
transform
を再実行する。 - 非純粋パイプ (Impure Pipe) は「変更検知サイクルごとに」
transform
を再実行する。
(2) 純粋パイプと非純粋パイプ
- 純粋パイプ (Pure Pipe)
- デフォルトではこちら。
@Pipe({ name: 'xxx' })
と書くだけで純粋パイプとして扱われる。 - 値が変わったときだけ再評価されるので、パフォーマンスによい。
- 単純な日付変換や数値フォーマットなど、入力値が変わらないときに「結果も変わらない」処理に最適。
- デフォルトではこちら。
- 非純粋パイプ (Impure Pipe)
@Pipe({ name: 'xxx', pure: false })
のように、pure: false
を指定すると非純粋パイプになる。- 変更検知サイクルのたびに毎回
transform
が走るので、配列やオブジェクトの“中身”の変化に対しても都度再評価される。 - 利用シーン:
- 配列・オブジェクトをテンプレート上でフィルタやソートしたいときに、「単純に参照が新規になった/ならない」を超えて、内容の微細な変化に対応させたい場合など。
- ただしパフォーマンスに影響するので、よく検討してから使う。
非純粋パイプの簡単な例
@Pipe({ name: 'search', pure: false // 非純粋パイプ }) export class SearchPipe implements PipeTransform { transform(items: any[], keyword: string): any[] { return items.filter(item => item.name.includes(keyword)); } }
この例では、items
(配列) や keyword
(検索文字列) が細かく変化するたびにパイプを再評価し、フィルタ済みの配列を返す。もし純粋パイプだと、配列の「参照」が同じままでは再実行されないため、意図するタイミングで結果が更新されない場合がある。とはいえ「毎回呼ばれる」ことになるため、大きな配列・複雑な処理には注意が必要です。
具体的な利用シーンとコード例
シーン1: ユーザー一覧の表示をフィルタ/ソートしたい
- パイプでフィルタ処理を書けば、テンプレートに
| filterUser:'xxx'
のように書くだけで表示を切り替えられる。 - ただし大量データがある場合は、都度全件フィルタするとパフォーマンスが落ちるため、コンポーネント側でフィルタした結果を保持するなど検討が必要。
@Pipe({ name: 'filterUser' }) export class FilterUserPipe implements PipeTransform { transform(users: { id: number; name: string; }[], searchTerm: string): any[] { if (!searchTerm) return users; return users.filter(user => user.name.toLowerCase().includes(searchTerm.toLowerCase()) ); } }
<!-- テンプレート --> <input [(ngModel)]="keyword" placeholder="検索ワード" /> <ul> <li *ngFor="let user of users | filterUser:keyword"> {{ user.name }} </li> </ul>
シーン2: 日付の差分を表示したい (たとえば「xx日前」「xx時間前」)
- ビルトインの
DatePipe
では「相対的な日付表示」はサポートしていない (locale, format によるもの) ので、カスタムパイプで実装する。
@Pipe({ name: 'relativeTime' }) export class RelativeTimePipe implements PipeTransform { transform(value: Date): string { const now = new Date(); const diffMs = now.getTime() - value.getTime(); const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24)); const diffHours = Math.floor(diffMs / (1000 * 60 * 60)); if (diffDays > 0) { return `${diffDays}日前`; } else if (diffHours > 0) { return `${diffHours}時間前`; } else { return 'たった今'; } } }
<!-- テンプレート例 --> <p>投稿時刻: {{ post.createdAt | relativeTime }}</p> <!-- 出力例: "1日前" "2時間前" ... -->
パイプは「コンポーネント → テンプレート」の単方向データフローの、テンプレート直前でのフォーマット処理 としてとらえると理解しやすいです。
前提として「Angularの変更検知の流れ (コンポーネントのプロパティが変わるとテンプレートに反映される)」を押さえておけば、純粋パイプと非純粋パイプの振る舞いも自然と理解できるでしょう。一度使ってみるとその便利さを実感できるので、まずはビルトインパイプの日付フォーマットや数値フォーマットから試し、必要に応じてカスタムパイプでオリジナルの変換処理を実装してみてください。