← 解説「サービスとDI — ロジックの置き場所」に戻る
サンプルコードで身につける: サービスとDI — ロジックの置き場所
解説で学んだ概念を、5つの具体例で確認します。素朴な例から実務寄りの例へと進みます。
1最小のサービスを定義する
サービスの最小例で、@Injectable を付けたただのクラスです。console.log を画面のあちこちに直書きせず、ログの出し方をこの1箇所に集約できます。「画面に関係しないロジックの置き場所」というサービスの役割が最も素朴に表れた形です。
import { Injectable } from '@angular/core';
// @Injectable を付けると、DIが管理できるクラスになる
@Injectable({ providedIn: 'root' })
export class LoggerService {
log(message: string) {
console.log('[app]', message);
}
}
// ログの形式を変えたくなっても、修正はこのクラスだけで済む
2計算ロジックをサービスに切り出す
税込価格や割引の計算のように、複数の画面で使う計算ルールはサービスにまとめます。コンポーネントごとに同じ計算式をコピーすると、税率変更のときに直し漏れが起きるからです。「ビジネスルールはサービス、表示はコンポーネント」という実務の分担を示す例です。
import { Injectable } from '@angular/core';
@Injectable({ providedIn: 'root' })
export class PriceService {
private taxRate = 0.1;
withTax(price: number) {
return Math.floor(price * (1 + this.taxRate));
}
discounted(price: number, rate: number) {
return Math.floor(price * (1 - rate));
}
}
// 税率が変わっても、修正するのはこのサービスだけ
3画面をまたいでデータを共有する
providedIn: 'root' のサービスはアプリ全体で1つだけ作られるため、コンポーネントをまたいだデータの置き場所になります。商品ページで add した閲覧履歴を、トップページで list して表示する、という画面間の連携がこの仕組みで実現できます。シングルトンという性質が実務でどう効くかが分かる例です。
import { Injectable } from '@angular/core';
// providedIn: 'root' なのでアプリ全体で1つだけ作られる
@Injectable({ providedIn: 'root' })
export class RecentlyViewedService {
private productIds: number[] = [];
add(productId: number) {
// 先頭に追加し、直近5件だけを残す
this.productIds = [productId, ...this.productIds].slice(0, 5);
}
list() {
return this.productIds;
}
}
// 商品ページで add し、トップページで list する、という共有ができる
4シグナルを持つ状態管理サービス
サービスの中でシグナルと computed を持たせると、アプリ全体で共有されるリアクティブな状態置き場になります。どの画面から add しても、件数を表示しているすべての場所が自動で更新されます。モダンAngularの状態管理は、まずこの「シグナル入りサービス」から始めるのが定石です。
import { Injectable, computed, signal } from '@angular/core';
@Injectable({ providedIn: 'root' })
export class CartStateService {
private items = signal<string[]>([]);
// 外には読み取り専用の派生値として公開する
readonly count = computed(() => this.items().length);
add(itemName: string) {
this.items.update((list) => [...list, itemName]);
}
}
// どの画面から add しても、count を表示している場所がすべて更新される
5お気に入り管理サービスを設計する
「追加済みなら外し、未追加なら入れる」というトグル動作を持つ、お気に入り機能の実務的なサービスです。判定(has)と更新(toggle)をサービスに集約しているので、お気に入りボタンを置くどの画面も同じルールで動きます。ECサイトやブックマーク機能の縮図といえる設計です。
import { Injectable, signal } from '@angular/core';
@Injectable({ providedIn: 'root' })
export class FavoriteService {
private ids = signal<number[]>([]);
toggle(productId: number) {
// 追加済みなら外し、未追加なら入れる
this.ids.update((current) =>
current.includes(productId)
? current.filter((id) => id !== productId)
: [...current, productId],
);
}
has(productId: number) {
return this.ids().includes(productId);
}
}