Angularのパイプとは、テンプレートの中で {{ 値 | パイプ名 }} と書いて、値の「見た目」を整える仕組みです。 日付を 2026/10/03 にする、数値に桁区切りを入れる、Observable の中身を取り出す、といった表示用の変換を、コンポーネントのクラスに処理を足さずに書けます。

この記事では、組み込みパイプ13種すべてを「書式 → 実際の出力」の表にまとめ、date の書式記号、日本語ロケール(ja)を登録したときの出力の違い、引数付きのカスタムパイプの作り方、pure と impure の違いを呼び出し回数のログで確かめた結果まで扱います。表の出力は、すべて下の環境で実際に描画した文字列です。

確認環境:Ubuntu 24.04 / Node.js 24.19.0 / Angular 22.2.1(@angular/cli 22.2.1 の ng new で作成)/ TypeScript 6.0.3 / Vitest 5.0.3(ng test)/ Chromium 141(Playwright 1.56.0)。タイムゾーンは Asia/Tokyo、日付の例は 2026年10月3日(土)14時5分9秒 078 ミリ秒です。

組み込みパイプの早見表

@angular/common が公開しているパイプは次の13種です(22.2.1 の公開名を列挙して確かめました)。出力は既定のロケール(en-US)のものです。

パイプ クラス名 書き方の例 出力
date DatePipe {{ d | date: 'yyyy/MM/dd HH:mm' }} 2026/10/03 14:05
number DecimalPipe {{ 1234.5678 | number: '1.0-2' }} 1,234.57
percent PercentPipe {{ 0.256 | percent: '1.1-1' }} 25.6%
currency CurrencyPipe {{ 1980 | currency: 'JPY' }} ¥1,980
uppercase UpperCasePipe {{ 'angular pipe GUIDE' | uppercase }} ANGULAR PIPE GUIDE
lowercase LowerCasePipe {{ 'angular pipe GUIDE' | lowercase }} angular pipe guide
titlecase TitleCasePipe {{ 'angular pipe GUIDE' | titlecase }} Angular Pipe Guide
slice SlicePipe {{ ['a','b','c','d','e'] | slice: 1 : 3 }} b,c
json JsonPipe {{ { id: 1 } | json }} 整形した JSON 文字列
keyvalue KeyValuePipe @for (e of obj | keyvalue; track e.key) { key, value } の配列(キー順)
async AsyncPipe {{ value$ | async }} Observable / Promise の最新の値
i18nPlural I18nPluralPipe {{ 5 | i18nPlural: map }} 5件あります
i18nSelect I18nSelectPipe {{ 'admin' | i18nSelect: map }} 管理者

使うパイプは、コンポーネントの imports にクラス名で追加します。 引数はコロン(:)で区切って渡し、パイプは | でいくつでもつなげられます。

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Angularパイプ一覧|date・number・asyncとカスタムパイプ」

パイプの基本の書き方

imports に追加してテンプレートで使う

よく使うパイプをひととおり入れた、そのまま貼って動くコンポーネントです。

// src/app/pipes/order-summary.ts
import { AsyncPipe, CurrencyPipe, DatePipe, DecimalPipe, KeyValuePipe, PercentPipe, SlicePipe, UpperCasePipe } from '@angular/common';
import { Component } from '@angular/core';
import { of } from 'rxjs';
import { TruncatePipe } from './custom.pipes';

@Component({
  selector: 'app-order-summary',
  // 使うパイプだけを imports に並べる
  imports: [AsyncPipe, CurrencyPipe, DatePipe, DecimalPipe, KeyValuePipe, PercentPipe, SlicePipe, UpperCasePipe, TruncatePipe],
  template: `
    <h2>{{ order.code | uppercase }}</h2>
    <p>注文日: {{ order.orderedAt | date: 'yyyy/MM/dd(EEE) HH:mm' }}</p>
    <p>合計: {{ order.total | currency: 'JPY' }}({{ order.count | number }}点)</p>
    <p>割引率: {{ order.discount | percent: '1.1-1' }}</p>
    <p>メモ: {{ order.note | truncate: 12 }}</p>
    <ul>
      @for (item of order.items | slice: 0 : 2; track item) {
        <li>{{ item }}</li>
      }
    </ul>
    <dl>
      @for (entry of order.options | keyvalue; track entry.key) {
        <dt>{{ entry.key }}</dt>
        <dd>{{ entry.value }}</dd>
      }
    </dl>
    <p>状態: {{ status$ | async }}</p>
  `,
})
export class OrderSummary {
  readonly order = {
    code: 'ord-0042',
    orderedAt: new Date(2026, 9, 3, 14, 5),
    total: 12800,
    count: 1200,
    discount: 0.125,
    note: '午前中の配達を希望します。不在のときは宅配ボックスへ',
    items: ['ノート', 'ペン', '付せん'],
    options: { wrapping: 'あり', receipt: 'なし' },
  };
  readonly status$ = of('発送準備中');
}

既定のロケール(en-US)で描画したテキストです。

ORD-0042
注文日: 2026/10/03(Sat) 14:05
合計: ¥12,800(1,200点)
割引率: 12.5%
メモ: 午前中の配達を希望します…
ノート
ペン
receipt
なし
wrapping
あり
状態: 発送準備中

曜日が Sat になっています。これを「土」にするには、あとで説明する ja ロケールの登録が必要です。登録後は次のように変わりました(変わった行だけ抜き出しています)。

注文日: 2026/10/03(土) 14:05
合計: ¥12,800(1,200点)

引数・連結・優先順位

書き方 意味 出力
{{ d | date }} 引数なし Oct 3, 2026
{{ d | date: 'EEEE' }} 引数1つ Saturday
{{ d | date: 'yyyy-MM-dd HH:mm' : 'UTC' }} 引数2つ(コロンで区切る) 2026-10-03 05:05
{{ d | date: 'EEEE' | uppercase }} 連結(左から順に適用) SATURDAY
{{ (one === 1 ? 'yes' : 'no') | uppercase }} かっこで囲んだ三項演算子の結果に適用 YES
{{ one === 1 ? 'yes' : 'no' | uppercase }} かっこなし:'no' だけに適用される yes
{{ nul ?? 'none' | uppercase }} ?? の結果に適用 NONE

三項演算子と組み合わせるときは、かっこで囲みます。 公式ドキュメントには、パイプ演算子は + - && || ?? などの二項演算子より優先順位が低く、三項演算子よりは高い、と書かれています。上の表の6行目と7行目は、そのとおりの結果になっています。

date パイプの書式と出力

定義済みの書式(en-US と ja の出力)

date の第1引数には、定義済みの名前か、書式記号の組み合わせを渡します。

書式 en-US の出力 ja の出力
(省略)= 'mediumDate' Oct 3, 2026 2026/10/03
'short' 10/3/26, 2:05 PM 2026/10/03 14:05
'medium' Oct 3, 2026, 2:05:09 PM 2026/10/03 14:05:09
'long' October 3, 2026, 2:05:09 PM GMT+9 2026年10月3日 14:05:09 GMT+9
'full' Saturday, October 3, 2026, 2:05:09 PM GMT+09:00 2026年10月3日土曜日 14時05分09秒 GMT+09:00
'shortDate' 10/3/26 2026/10/03
'mediumDate' Oct 3, 2026 2026/10/03
'longDate' October 3, 2026 2026年10月3日
'fullDate' Saturday, October 3, 2026 2026年10月3日土曜日
'shortTime' 2:05 PM 14:05
'mediumTime' 2:05:09 PM 14:05:09
'longTime' 2:05:09 PM GMT+9 14:05:09 GMT+9
'fullTime' 2:05:09 PM GMT+09:00 14時05分09秒 GMT+09:00

定義済みの書式はロケールによって並びや区切りが変わります。 画面の表示を固定したいときは、次の書式記号で直接指定します。

書式記号の早見表

記号 意味 en-US の出力 ja の出力
yyyy / yy / y 年(4桁 / 2桁 / 最小桁) 2026 / 26 / 2026 同じ
MM / M 月(0埋め / そのまま) 10 / 10 同じ
MMM 月の短い名前 Oct 10月
MMMM 月の名前 October 10月
dd / d 日(0埋め / そのまま) 03 / 3 同じ
EEE 曜日の短い名前 Sat 土
EEEE 曜日の名前 Saturday 土曜日
HH 時(0〜23、0埋め) 14 同じ
h 時(1〜12) 2 同じ
mm / ss 分 / 秒(0埋め) 05 / 09 同じ
SSS ミリ秒 078 同じ
a 午前・午後 PM 午後
Z タイムゾーン(基本形式) +0900 同じ
ZZZZZ タイムゾーン(コロン付き) +09:00 同じ
z / zzzz タイムゾーン名(短い / 長い) GMT+9 / GMT+09:00 同じ
w 年内の週番号 40 同じ
G 紀元 AD 西暦

よく使う組み合わせと、その出力です。

書式 en-US の出力 ja の出力
'yyyy-MM-dd' 2026-10-03 同じ
'yyyy/MM/dd HH:mm:ss' 2026/10/03 14:05:09 同じ
'yy/M/d' 26/10/3 同じ
'HH:mm:ss.SSS' 14:05:09.078 同じ
'yyyy-MM-ddTHH:mm:ssZZZZZ' 2026-10-03T14:05:09+09:00 同じ
'MMM d, y' Oct 3, 2026 10月 3, 2026
'h:mm a' 2:05 PM 2:05 午後
'aaaa h時mm分' — 午後 2時05分
'yyyy年M月d日(EEE)' — 2026年10月3日(土)
'M/d(EEEEE)' — 10/3(土)
'G y' AD 2026 西暦 2026

「年」「月」「日」のような漢字は、書式の中にそのまま書いて出力できました。記号はこのほかにも、単独表記の月(L)や曜日(c)、月内の週(W)などが公式の API ドキュメントの表に載っています。なお、'B h:mm'(時間帯を表す記号)は ja では次のエラーになりました。追加のロケールデータが必要な記号です。

NG02100: InvalidPipeArgument: 'NG02303: Missing extra locale data for the locale "ja". Use "registerLocaleData" to load new data. See the "I18n guide" on angular.io to know more.' for pipe 'DatePipe'

渡せる値とタイムゾーン

書き方 出力 ポイント
{{ '2026-10-03T05:05:09Z' | date: 'yyyy-MM-dd HH:mm' }} 2026-10-03 14:05 ISO 形式の文字列を渡せる。表示は実行環境のタイムゾーン
{{ 1790000000000 | date: 'yyyy-MM-dd HH:mm' : 'UTC' }} 2026-09-21 14:13 ミリ秒の数値も渡せる
{{ '2026-10-03' | date: 'yyyy/MM/dd HH:mm' }} 2026/10/03 00:00 日付だけの文字列は、その日の 0 時として扱われた
{{ d | date: 'yyyy-MM-dd HH:mm' : 'UTC' }} 2026-10-03 05:05 第2引数でタイムゾーンを指定
{{ d | date: 'yyyy-MM-dd HH:mm' : '+0900' }} 2026-10-03 14:05 オフセットでも指定できる
{{ d | date: 'yyyy-MM-dd HH:mm' : '-0500' }} 2026-10-03 00:05 同上
{{ nul | date }} (空文字) null はエラーにならず空になる
{{ 'abc' | date }} エラー 日付に変換できない文字列

最後の行のエラー文は次のとおりです。

NG02100: InvalidPipeArgument: 'NG02311: Unable to convert "abc" into a date' for pipe 'DatePipe'

タイムゾーンを省略すると、利用者の端末のタイムゾーンで表示されます。 公式の API ドキュメントにも、指定がなければ DATE_PIPE_DEFAULT_OPTIONS の値を見て、それもなければ端末のローカルのタイムゾーンを使う、と書かれています。

ja ロケールを登録して日本語の表記にする

登録前に LOCALE_ID を ja にするとエラーになる

LOCALE_ID だけを 'ja' にして date と number を使うと、次のエラーになりました。

NG02100: InvalidPipeArgument: 'NG0701: Missing locale data for the locale "ja".' for pipe 'DatePipe'
NG02100: InvalidPipeArgument: 'NG0701: Missing locale data for the locale "ja".' for pipe 'DecimalPipe'

Angular に最初から入っているロケールデータは en-US だけなので、ja のデータを registerLocaleData で登録してから LOCALE_ID を切り替えます。

app.config.ts に3行足す

// src/app/app.config.ts(ロケールに関係する部分)
import { registerLocaleData } from '@angular/common';
import localeJa from '@angular/common/locales/ja';
import { ApplicationConfig, DEFAULT_CURRENCY_CODE, LOCALE_ID, provideBrowserGlobalErrorListeners } from '@angular/core';

registerLocaleData(localeJa);

export const appConfig: ApplicationConfig = {
  providers: [
    provideBrowserGlobalErrorListeners(),
    { provide: LOCALE_ID, useValue: 'ja' },
    { provide: DEFAULT_CURRENCY_CODE, useValue: 'JPY' },
  ],
};

この設定で ng build したアプリを Chromium で開き、{{ today | date: 'fullDate' }} と {{ 1980 | currency }} の表示を読み取った結果です。

date  = 2026年10月3日土曜日 / price = ¥1,980

ロケールで変わるもの・変わらないもの

書き方 en-US ja 補足
{{ d | date }} Oct 3, 2026 2026/10/03 定義済み書式は並びごと変わる
{{ d | date: 'EEEE' }} Saturday 土曜日 月名・曜日名・午前午後が変わる
{{ 1234.5678 | number }} 1,234.568 1,234.568 桁区切りと小数点は同じ記号
{{ 0.256 | percent }} 26% 26% 同じ
{{ 1980 | currency }} $1,980.00 $1,980.00 既定の通貨コードは USD のまま
{{ 1980 | currency: 'JPY' }} ¥1,980 ¥1,980 ja では全角の円記号になった
{{ 1980 | currency }} と DEFAULT_CURRENCY_CODE = JPY ¥1,980 ¥1,980 通貨コードを省略できる

LOCALE_ID を ja にしても、currency の既定の通貨は USD のままでした。円で表示したいときは、引数で 'JPY' を渡すか、DEFAULT_CURRENCY_CODE を登録します。

そのほか、次の設定と書き方も動かして確かめました。

やりたいこと 書き方 出力
1か所だけ別のロケールで表示する {{ d | date: 'fullDate' : '' : 'en-US' }}(LOCALE_ID は ja) Saturday, October 3, 2026
date の既定の書式とタイムゾーンを変える { provide: DATE_PIPE_DEFAULT_OPTIONS, useValue: { dateFormat: 'yyyy/MM/dd HH:mm', timezone: 'UTC' } } {{ d | date }} が 2026/10/03 05:05

number・percent・currency の書式と出力

桁の指定は「整数の最小桁.小数の最小桁-小数の最大桁」

number・percent・currency の桁の指定は共通で、'1.0-2' のような文字列で渡します。

書き方 出力 読み方
{{ 1234.5678 | number }} 1,234.568 省略時は小数3桁まで
{{ 1234.5678 | number: '1.0-0' }} 1,235 小数なし(四捨五入)
{{ 1234.5678 | number: '1.2-2' }} 1,234.57 小数をちょうど2桁
{{ 1234.5678 | number: '1.0-2' }} 1,234.57 小数は最大2桁
{{ 1234.5678 | number: '3.1-1' }} 1,234.6 小数をちょうど1桁
{{ 1234.5678 | number: '1.4-4' }} 1,234.5678 小数をちょうど4桁
{{ 1234567.891 | number: '1.0-0' }} 1,234,568 桁区切りは自動
{{ 5 | number: '2.0' }} 05 整数部を2桁に0埋め
{{ 2.5 | number: '1.0-0' }} 3 端数の 5 は切り上げ
{{ 3.5 | number: '1.0-0' }} 4 同上
{{ 1.005 | number: '1.2-2' }} 1.01 同上
{{ '1234.5' | number }} 1,234.5 数値に読める文字列も渡せる
{{ nul | number }} (空文字) null は空になる
{{ 'abc' | number }} エラー 下のエラー文を参照
NG02100: InvalidPipeArgument: 'NG02309: abc is not a number' for pipe 'DecimalPipe'

percent

書き方 出力
{{ 0.256 | percent }} 26%
{{ 0.256 | percent: '1.1-1' }} 25.6%
{{ 0.256 | percent: '1.2-2' }} 25.60%
{{ 1.5 | percent }} 150%

percent は値を100倍して % を付けます。すでに 25.6 のような百分率の数値を持っている場合は、number に % を自分で付けるほうが合っています。

currency

引数は「通貨コード : 表示方法 : 桁の指定」の順です。出力は en-US のものです。

書き方 出力 ポイント
{{ 1980 | currency }} $1,980.00 既定は USD
{{ 1980 | currency: 'JPY' }} ¥1,980 円は小数なしになる
{{ 1980 | currency: 'JPY' : 'code' }} JPY1,980 通貨コードで表示
{{ 1980 | currency: 'EUR' }} €1,980.00
{{ 1980 | currency: 'CAD' }} CA$1,980.00 'symbol' を指定したときと同じ表示
{{ 1980 | currency: 'CAD' : 'symbol-narrow' }} $1,980.00 短い記号
{{ 1980 | currency: 'JPY' : '円' }} 円1,980 任意の文字列は前に付く
{{ 1980 | currency: 'JPY' : '' }} 1,980 記号なし
{{ 1234.5678 | currency: 'USD' : 'symbol' : '1.0-0' }} $1,235 桁の指定は第3引数
{{ 1234.5678 | currency: 'JPY' }} ¥1,235 円は自動で整数に丸められた

「1,980円」のように後ろへ単位を付けたいときは、{{ 1980 | number }}円 と書くのが手軽です。

文字列・配列・オブジェクト用のパイプ

uppercase・lowercase・titlecase・slice

書き方 出力
{{ 'angular pipe GUIDE' | uppercase }} ANGULAR PIPE GUIDE
{{ 'angular pipe GUIDE' | lowercase }} angular pipe guide
{{ 'angular pipe GUIDE' | titlecase }} Angular Pipe Guide
{{ 'angular pipe GUIDE' | slice: 0 : 7 }} angular
{{ 'angular pipe GUIDE' | slice: -5 }} GUIDE
{{ ['a','b','c','d','e'] | slice: 1 : 3 }} b,c
{{ ['a','b','c','d','e'] | slice: -2 }} d,e

slice は JavaScript の slice() と同じ「開始位置 : 終了位置(含まない)」の指定で、負の数は末尾からの位置になります。@for (item of items | slice: 0 : 2; track item) のように、一覧の先頭だけを表示する用途でよく使います。

json(中身を確認する)

{{ obj | json }} の出力(obj = { id: 1, tags: ['x', 'y'] })
{
  "id": 1,
  "tags": [
    "x",
    "y"
  ]
}

インデント付きで出力されるので、pre 要素の中に置くと読みやすくなります。開発中に値を確かめるためのパイプです。{{ nul | json }} は null と表示されました。

keyvalue(オブジェクトを @for で回す)

{ math: 80, art: 95, english: 70 } というオブジェクトを渡したときの並び順です。

書き方 並び順 補足
scores | keyvalue art → english → math 既定はキーの昇順(定義した順ではない)
scores | keyvalue: keep math → art → english keep = () => 0 を渡すと定義した順のまま
scores | keyvalue: desc art(95) → math(80) → english(70) desc = (a, b) => b.value - a.value で値の降順

第1引数には、配列の sort() に渡すのと同じ形の比較関数を渡せます。関数はコンポーネントのプロパティとして定義しておきます。

i18nPlural・i18nSelect(値に応じて文言を切り替える)

マップ 値 出力
{ '=0': '0件です', '=1': '1件だけ', other: '#件あります' } 0 0件です
同上 1 1件だけ
同上 5 5件あります
{ admin: '管理者', member: '一般会員', other: 'ゲスト' } 'admin' 管理者
同上 'x' ゲスト

i18nPlural は件数に応じた文言、i18nSelect はキーに応じた文言を返します。# は数値に置き換わり、どれにも当てはまらないときは other が使われました。テンプレートに @if を並べるかわりに、文言の対応表を1つ持つだけで済みます。

async パイプで Observable と Promise を表示する

async は、Observable や Promise を購読して最新の値を返し、コンポーネントが破棄されるときに購読を自動で解除するパイプです。

import { AsyncPipe } from '@angular/common';
import { Component } from '@angular/core';
import { Subject } from 'rxjs';

@Component({ imports: [AsyncPipe], template: `<p>{{ value$ | async }}</p><p>{{ (user$ | async)?.name }}</p><p>{{ promise | async }}</p>
  @if (user$ | async; as user) { <b>{{ user.name }}</b> } @else { <b>読み込み中</b> }` })
class AsyncHost {
  value$ = new Subject<number>();
  user$ = new Subject<{ name: string }>();
  promise = new Promise<string>((r) => setTimeout(() => r('done'), 10));
}

4つの要素のテキストを、値が届く前・届いた後・Promise の解決後に出力した結果です。

値が届く前 : "" | "" | "" | "読み込み中"
next の後   : "1" | "Sato" | "" | "Sato"
Promise 解決: "1" | "Sato" | "done" | "Sato"
購読数(破棄前)= 2
購読数(破棄後)= 0
ポイント 内容
値が届くまでは null プロパティを読むときは (user$ | async)?.name のように ?. を付ける
@if (user$ | async; as user) 取り出した値に名前を付けて、ブロックの中で何度も使える
同じ Observable に async を2回書くと購読も2回 上の出力で購読数が 2 になっている。as で1回にまとめるとよい
破棄で購読が解除される 購読数が 0 に戻っている。自分で unsubscribe を書かなくてよい

カスタムパイプの作り方

引数付きのパイプを作る

カスタムパイプは、@Pipe({ name: '...' }) を付けたクラスに transform メソッドを書くだけです。transform の第1引数が | の左側の値、第2引数以降がコロンで渡した引数になります。

// src/app/pipes/custom.pipes.ts
import { Pipe, PipeTransform } from '@angular/core';

/** 長い文字列を指定の長さで切り、末尾に記号を付ける */
@Pipe({ name: 'truncate' })
export class TruncatePipe implements PipeTransform {
  transform(value: string | null | undefined, limit = 20, suffix = '…'): string {
    if (!value) return '';
    return value.length <= limit ? value : value.slice(0, limit) + suffix;
  }
}

/** バイト数を KB / MB などに直す */
@Pipe({ name: 'fileSize' })
export class FileSizePipe implements PipeTransform {
  transform(bytes: number | null | undefined, digits = 1): string {
    if (bytes == null || Number.isNaN(bytes)) return '';
    const units = ['B', 'KB', 'MB', 'GB', 'TB'];
    let n = bytes;
    let i = 0;
    while (n >= 1024 && i < units.length - 1) {
      n /= 1024;
      i++;
    }
    return `${i === 0 ? n : n.toFixed(digits)} ${units[i]}`;
  }
}

export const calls = { pure: 0, impure: 0 };

@Pipe({ name: 'sumPure' }) // pure: true が既定
export class SumPurePipe implements PipeTransform {
  transform(values: number[]): number {
    calls.pure++;
    return values.reduce((a, b) => a + b, 0);
  }
}

@Pipe({ name: 'sumImpure', pure: false })
export class SumImpurePipe implements PipeTransform {
  transform(values: number[]): number {
    calls.impure++;
    return values.reduce((a, b) => a + b, 0);
  }
}

後半の SumPurePipe と SumImpurePipe は、次の章で pure と impure を比べるためのものです。使う側は、組み込みパイプと同じように imports へ追加します。

書き方 出力
{{ long | truncate }} Angularのパイプはテンプレートの中…
{{ long | truncate: 10 }} Angularのパイ…
{{ long | truncate: 10 : '...' }} Angularのパイ...
{{ 'short' | truncate: 10 }} short
{{ nul | truncate }} (空文字)
{{ 5300000 | fileSize }} 5.1 MB
{{ 5300000 | fileSize: 2 }} 5.05 MB
{{ 512 | fileSize }} 512 B

(long は「Angularのパイプはテンプレートの中で値の見た目を整える仕組みです」という文字列です。)

決めること おすすめ 理由
name キャメルケース(fileSize) 公式ドキュメントの推奨。ハイフンは使わない
クラス名 name をパスカルケースにして Pipe を付ける(FileSizePipe) 同上
引数の既定値 limit = 20 のように付ける 引数なしでも使える
null / undefined 受け取って空文字を返す async の最初の値は null になる
戻り値の型 明示する テンプレートの型チェックが効く

サービスや LOCALE_ID を使うパイプ

パイプのクラスでも inject() が使えます。LOCALE_ID を受け取り、ブラウザー標準の Intl.RelativeTimeFormat で「3日前」のような表記を返す例です。

// src/app/pipes/relative.pipe.ts
import { LOCALE_ID, Pipe, PipeTransform, inject } from '@angular/core';

/** 「3日前」「2時間後」のような相対表記。LOCALE_ID を注入して使う */
@Pipe({ name: 'relativeTime' })
export class RelativeTimePipe implements PipeTransform {
  private readonly rtf = new Intl.RelativeTimeFormat(inject(LOCALE_ID), { numeric: 'auto' });

  transform(value: Date | string | number, now: Date = new Date()): string {
    const diffSec = Math.round((new Date(value).getTime() - now.getTime()) / 1000);
    const table: [Intl.RelativeTimeFormatUnit, number][] = [
      ['day', 86400],
      ['hour', 3600],
      ['minute', 60],
    ];
    for (const [unit, sec] of table) {
      if (Math.abs(diffSec) >= sec) return this.rtf.format(Math.round(diffSec / sec), unit);
    }
    return this.rtf.format(diffSec, 'second');
  }
}

3日前の日時を渡したときの出力です。

LOCALE_ID {{ past | relativeTime: d }} の出力
en-US(既定) 3 days ago
ja 3 日前

エラーになる例と直し方

imports への追加を忘れたときと、引数の型が合わないときの ng build のエラーです。

✘ [ERROR] NG8004: No pipe found with name 'truncate'. [plugin angular-compiler]
✘ [ERROR] TS2345: Argument of type 'string' is not assignable to parameter of type 'number'. [plugin angular-compiler]
✘ [ERROR] TS2345: Argument of type 'number' is not assignable to parameter of type 'string'. [plugin angular-compiler]
エラー 書いたコード 直し方
NG8004 {{ title | truncate: 10 }}(imports が空) コンポーネントの imports に TruncatePipe を追加する
TS2345(1つ目) {{ title | truncate: 'ten' }} 引数を数値にする(truncate: 10)
TS2345(2つ目) {{ count | truncate }}(count は数値) 文字列を渡すか、パイプ側で数値も受け取れるようにする

transform の引数に型を付けておくと、テンプレートの書き間違いをビルドの時点で見つけられます。

テストの書き方

依存のないパイプは、new して transform を呼ぶだけで確かめられます。

// src/app/pipes/truncate.pipe.spec.ts
import { Component } from '@angular/core';
import { TestBed } from '@angular/core/testing';
import { describe, expect, it } from 'vitest';
import { FileSizePipe, TruncatePipe } from './custom.pipes';

describe('TruncatePipe', () => {
  const pipe = new TruncatePipe(); // 依存がないパイプは new するだけで試せる

  it('上限を超えたら切って記号を付ける', () => {
    expect(pipe.transform('abcdefghij', 5)).toBe('abcde…');
    expect(pipe.transform('abcdefghij', 5, '...')).toBe('abcde...');
  });

  it('上限以内ならそのまま、null は空文字', () => {
    expect(pipe.transform('abc', 5)).toBe('abc');
    expect(pipe.transform(null)).toBe('');
  });

  it('テンプレートの中で引数付きで使える', () => {
    @Component({
      imports: [TruncatePipe, FileSizePipe],
      template: `<p>{{ title | truncate: 8 : '...' }} / {{ size | fileSize: 2 }}</p>`,
    })
    class Host {
      title = 'Angular pipe guide';
      size = 5300000;
    }
    const fixture = TestBed.createComponent(Host);
    fixture.detectChanges();
    expect(fixture.nativeElement.textContent).toBe('Angular ... / 5.05 MB');
  });
});
 ✓ |demo| src/app/pipes/truncate.pipe.spec.ts > TruncatePipe > 上限を超えたら切って記号を付ける 1ms
 ✓ |demo| src/app/pipes/truncate.pipe.spec.ts > TruncatePipe > 上限以内ならそのまま、null は空文字 0ms
 ✓ |demo| src/app/pipes/truncate.pipe.spec.ts > TruncatePipe > テンプレートの中で引数付きで使える 8ms

pure と impure の違いを呼び出し回数で確かめる

pure パイプは「参照が変わったとき」だけ再計算する

パイプは既定で pure です。公式ドキュメントでは、pure パイプは、文字列や数値などの値が変わったとき、または配列やオブジェクトなどの参照が変わったときにだけ実行される、と説明されています。配列に push しても参照は同じなので、再計算されません。

前の章の SumPurePipe(pure)と SumImpurePipe(pure: false)を同じ配列に使い、transform が呼ばれた回数を数えます。

// src/app/pipes/pure-demo.spec.ts
import { ChangeDetectorRef, Component } from '@angular/core';
import { TestBed } from '@angular/core/testing';
import { expect, it } from 'vitest';
import { SumImpurePipe, SumPurePipe, calls } from './custom.pipes';

@Component({
  imports: [SumPurePipe, SumImpurePipe],
  template: `pure={{ items | sumPure }} impure={{ items | sumImpure }}`,
})
class SumHost {
  items = [1, 2, 3];
  other = 0;
}

it('pure と impure の呼び出し回数を比べる', () => {
  const fixture = TestBed.createComponent(SumHost);
  const host = fixture.componentInstance;
  const cdr = fixture.componentRef.injector.get(ChangeDetectorRef);
  const check = (label: string) => {
    cdr.markForCheck();
    fixture.detectChanges(); // 変更検知を 1 回走らせる
    console.log(`${label} | ${fixture.nativeElement.textContent} | pure=${calls.pure}回 impure=${calls.impure}回`);
  };

  calls.pure = calls.impure = 0;
  check('1. 初回表示');
  host.items.push(4);
  check('2. items.push(4)');
  host.other++;
  check('3. 無関係な値を変更');
  host.items = [...host.items, 5];
  check('4. 新しい配列に差し替え');
  check('5. 何も変えずに再チェック');

  expect(calls.pure).toBe(2);
  expect(calls.impure).toBe(5);
});
1. 初回表示 | pure=6 impure=6 | pure=1回 impure=1回
2. items.push(4) | pure=6 impure=10 | pure=1回 impure=2回
3. 無関係な値を変更 | pure=6 impure=10 | pure=1回 impure=3回
4. 新しい配列に差し替え | pure=15 impure=15 | pure=2回 impure=4回
5. 何も変えずに再チェック | pure=15 impure=15 | pure=2回 impure=5回
操作 pure の表示 pure の呼び出し impure の表示 impure の呼び出し
1. 初回表示 6 1回 6 1回
2. items.push(4) 6 のまま 1回 10 2回
3. 無関係な値を変更 6 のまま 1回 10 3回
4. items = [...items, 5] 15 2回 15 4回
5. 何も変えずに再チェック 15 2回 15 5回

pure パイプは push では再計算されず、表示が 6 のまま残りました。新しい配列に差し替えた時点で 15 に更新されています。 impure パイプは変更検知が走るたびに毎回呼ばれ、配列と関係のない値を変えたときも、何も変えていないときも回数が増えています。

どちらを選ぶかの判断表

状況 選ぶもの 理由
入力が文字列・数値・日付 pure(既定のまま) 値が変われば再計算される
入力が配列・オブジェクト pure のまま、新しい配列・オブジェクトに差し替える [...items, x] や items.filter(...) で参照が変わり、再計算される
入力が signal pure のまま、update((v) => [...v, x]) で新しい配列を返す 下の結果を参照
入力の中身が外から書き換えられ、差し替えにできない impure(pure: false) 毎回呼ばれるので、transform の中を軽く保つ
重い計算(並べ替え・集計) パイプではなく computed() などで先に計算しておく 呼び出し回数を自分で管理できる

公式ドキュメントは、impure パイプは使い方によって性能への影響が大きくなるため、本当に必要な場合に限るようすすめています。まずは「配列は差し替える」を基本にすると、pure のままで済みます。

signal に入れた配列でも同じ結果でした。{{ items() | sumPure }} に対して、同じ配列へ push して返した場合と、新しい配列を返した場合の表示です。

初回 = 6
update で push(同じ配列)= 6
update で新しい配列 = 15

TypeScript の中で同じ書式を使う

テンプレートではなくクラスの中で整形したいときは、@angular/common の関数か、パイプのインスタンスを使います。ja を登録した状態での出力です。

やり方 書き方 出力
関数で整形(日付) formatDate(d, 'yyyy年M月d日(EEE)', 'ja') 2026年10月3日(土)
関数で整形(数値) formatNumber(1234.5678, 'ja', '1.0-1') 1,234.6
パイプを注入して使う providers: [DatePipe] に登録し、inject(DatePipe).transform(d, 'longDate') 2026年10月3日

関数はロケールを引数で渡す形、注入したパイプは LOCALE_ID に従う形です。ファイル名や CSV の中身など、テンプレートの外で文字列を組み立てる場面で使えます。

よくある質問

Q. パイプとメソッド呼び出し({{ format(value) }})はどう使い分けますか? A. 表示用の変換はパイプにするのがおすすめです。pure パイプは入力が変わったときだけ transform が呼ばれることを、この記事の呼び出し回数のログで確かめました。同じ変換を複数のコンポーネントで使い回せる点も利点です。

Q. date パイプで曜日を日本語にするにはどうしますか? A. registerLocaleData(localeJa) で ja のデータを登録し、LOCALE_ID を 'ja' にします。そのうえで {{ d | date: 'yyyy年M月d日(EEE)' }} と書くと「2026年10月3日(土)」と表示されました。登録せずに LOCALE_ID だけ変えると NG0701 のエラーになります。

Q. currency で「¥」ではなく「円」を後ろに付けたいときは? A. currency の第2引数に文字列を渡すと、数値の前に付きます(円1,980)。後ろに付けたい場合は {{ price | number }}円 のように number と文字を組み合わせると、「1,980円」と書けます。

Q. 配列を追加したのにパイプの結果が変わりません。 A. pure パイプは配列の参照が変わったときだけ再計算します。items.push(x) のかわりに items = [...items, x] と新しい配列に差し替えると更新されます。この記事の実行結果でも、push のあとは 6 のままで、差し替えたあとに 15 へ変わりました。

まとめ

  • 組み込みパイプは13種。使うものをコンポーネントの imports にクラス名で追加する。
  • 引数はコロンで区切り、| で連結できる。三項演算子と組み合わせるときはかっこで囲む。
  • date は定義済み書式がロケールで変わる。表示を固定したいときは 'yyyy/MM/dd HH:mm' のように書式記号で指定する。
  • 日本語の曜日や月名には registerLocaleData(localeJa) と LOCALE_ID の登録が必要。 円の表示は 'JPY' か DEFAULT_CURRENCY_CODE で指定する。
  • number・percent・currency の桁は '整数の最小桁.小数の最小桁-小数の最大桁' で指定する。
  • カスタムパイプは @Pipe と transform だけで作れる。 引数に型と既定値を付け、null を受け取れるようにしておく。
  • pure パイプは push では再計算されない。配列は新しいものに差し替える。 impure は変更検知のたびに呼ばれるので、必要な場面に絞る。

参考・出典

  • Angular 公式ドキュメント「Pipes」 https://angular.dev/guide/templates/pipes
  • Angular 公式 API ドキュメント「DatePipe」 https://angular.dev/api/common/DatePipe

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Angularパイプ一覧|date・number・asyncとカスタムパイプ」

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?