12k
All articles

Angularで効果的に状態を管理する方法

Angular Signals、RxJS サービス、NgRx、SignalStore の中から、状態のスコープと実際のアプリケーション要件に基づいた実践的な選択基準を解説する。

OpenReplay Team
OpenReplay Team
Angularで効果的に状態を管理する方法

状態管理は、優雅にスケールするAngularアプリと保守の悪夢となるアプリの分かれ目です。しかし、ほとんどの開発者は根本的な疑問に悩んでいます:Signals、サービス、RxJS、NgRxのどれをいつ使うべきなのか?

この記事では、理論的なベストプラクティスではなく、実際のニーズに基づいて適切なAngular状態管理アプローチを選択するための実践的なフレームワークを提供します。Angular Signalsが輝く場面、RxJSを使ったサービスが適している場面、そしてNgRxやSignalStoreが必要になる場面を探ります。

重要なポイント

  • Angularアプリケーションには3つの状態レイヤーがあります:ローカルコンポーネント、共有フィーチャー、グローバルアプリケーション状態
  • Signalsは、Angular 16以降、同期的でリアクティブな状態に対する最もシンプルなソリューションを提供します
  • RxJSを使ったサービスは、フレームワークのオーバーヘッドなしで非同期操作と中程度の複雑性を処理します
  • NgRxとSignalStoreは、複雑な状態の相互作用に構造化されたパターンが必要な場合に価値を発揮します

Angularアプリケーションにおける状態のスコープを理解する

ツールを選択する前に、状態のスコープを理解しましょう。Angularアプリケーションには通常、3つの異なる状態レイヤーがあり、それぞれ異なる管理戦略が必要です。

ローカルコンポーネント状態

ローカル状態は単一のコンポーネントと共に生まれ、消滅します。フォーム入力、UIトグル、一時的な計算などが該当します。これらのシナリオでは、Angularコンポーネント状態のベストプラクティスはシンプルさを重視します:リアクティブな状態にはSignalsを、静的な値には通常のクラスプロパティを使用します。

@Component({
  selector: 'app-product-card',
  template: `
    <div [class.expanded]="isExpanded()">
      <button (click)="toggle()">{{ isExpanded() ? 'Less' : 'More' }}</button>
    </div>
  `
})
export class ProductCardComponent {
  isExpanded = signal(false);
  
  toggle() {
    this.isExpanded.update(v => !v);
  }
}

共有フィーチャー状態

フィーチャー内の複数のコンポーネントが同じデータを必要とする場合、サービスが調整ポイントになります。ヘッダー、サイドバー、チェックアウトページに表示されるショッピングカートは、一元管理が必要ですが、必ずしもグローバルストアは必要ありません。

グローバルアプリケーション状態

認証ステータス、ユーザー設定、アプリ全体の通知は、アプリケーション全体に影響します。ここでAngularでグローバル状態を管理するには、複雑性と保守性を慎重に検討する必要があります。

Angular Signals:リアクティブ状態のモダンなデフォルト

Angular 16以降、Signalsはobservableの複雑さなしに細粒度のリアクティビティを提供します。同期的な状態更新と計算値に優れています。

Signalsが優れている場面

以下のようなリアクティブな状態が必要な場合にSignalsを使用します:

  • 同期的に更新される
  • 計算値を効率的に導出する
  • Angularの変更検知とシームレスに統合される
@Injectable({ providedIn: 'root' })
export class CartService {
  private items = signal<CartItem[]>([]);
  
  // 計算されたsignalは自動的に更新されます
  total = computed(() => 
    this.items().reduce((sum, item) => sum + item.price * item.quantity, 0)
  );
  
  addItem(item: CartItem) {
    this.items.update(items => [...items, item]);
  }
}

主な利点は?Signalsは、サブスクリプション管理やメモリリークといったRxJSの一般的な落とし穴を排除しながら、粒度の細かい更新によってより優れたパフォーマンスを提供します。

RxJSを使用したサービスベースの状態管理

非同期操作や複雑なデータストリームには、RxJS observableが依然として非常に有用です。Angular SignalsとNgRxの議論では、この中間地点を見落としがちです:RxJSを使ったサービスは、フレームワークのオーバーヘッドなしで多くの実世界のシナリオを処理します。

SignalsとRxJSの組み合わせ

モダンなAngularアプリケーションは、ハイブリッドアプローチから恩恵を受けることが多いです:

@Injectable({ providedIn: 'root' })
export class UserService {
  private currentUser = signal<User | null>(null);
  
  // 読み取り専用signalとして公開
  user = this.currentUser.asReadonly();
  
  constructor(private http: HttpClient) {}
  
  loadUser(id: string) {
    return this.http.get<User>(`/api/users/${id}`).pipe(
      tap(user => this.currentUser.set(user)),
      catchError(error => {
        this.currentUser.set(null);
        return throwError(() => error);
      })
    );
  }
}

このパターンは、状態の保存にはSignalのシンプルさを、非同期操作にはRxJSのパワーを提供します。

NgRxとSignalStore:複雑性が構造を要求する場合

NgRxは、真に複雑な状態要件がある場合に意味を持ちます:マルチユーザーコラボレーション、楽観的更新、タイムトラベルデバッグ、または広範なクロスフィーチャー調整などです。

NgRx SignalStore:軽量な代替手段

NgRx SignalStoreとSignalsを組み合わせることで、ボイラープレートなしで構造を提供します。SignalStoreは、AngularのネイティブなSignalパフォーマンスを活用しながら、状態管理パターンを提供します:

export const CartStore = signalStore(
  { providedIn: 'root' },
  withState(initialState),
  withComputed((state) => ({
    itemCount: computed(() => state.items().length),
    isEmpty: computed(() => state.items().length === 0)
  })),
  withMethods((store) => ({
    addItem: (item: CartItem) => 
      patchState(store, { items: [...store.items(), item] }),
    clear: () => 
      patchState(store, { items: [] })
  }))
);

SignalStoreは、サービス以上の構造が必要だが、従来のNgRxほどの儀式は不要なアプリケーションにとって最適な選択肢です。

正しい選択をする:実践的な意思決定フレームワーク

「どの状態管理が最適か?」と問うのをやめて、「この特定の状態には何が必要か?」と問い始めましょう。

Signalsを直接使用する場合:

  • 状態が同期的でローカルである
  • 計算値が必要である
  • 頻繁な更新でパフォーマンスが重要である

SignalsまたはRxJSを使ったサービスを使用する場合:

  • 複数のコンポーネントが状態を共有する
  • 非同期操作を処理している
  • 状態ロジックがフィーチャー固有である

NgRx SignalStoreを検討する場合:

  • チーム全体で一貫したパターンが必要である
  • 状態の相互作用が複雑になる
  • 完全なReduxなしで構造が欲しい

従来のNgRxを採用する場合:

  • 厳格な要件を持つエンタープライズアプリケーションを構築している
  • 複数の開発者が予測可能なパターンを必要としている
  • DevTools、タイムトラベルデバッグ、またはエフェクトが必要である

避けるべき一般的な落とし穴

Angular状態管理における最大の間違いは、間違ったツールを選ぶことではなく、過剰設計することです。すべてのカウンターをNgRxに入れないでください。コンポーネント専用の状態にサービスを作成しないでください。Signalsで十分な場合にRxJSを使用しないでください。

シンプルに始めましょう。コンポーネントが状態を共有する場合にサービスに抽出します。サービスが扱いにくくなったらストアを導入します。この段階的なアプローチは、過小設計と過剰設計の両方を防ぎます。

結論

Angularにおける効果的な状態管理は、すべてに対して1つのアプローチを選択することではありません。モダンなAngularアプリケーションは、階層化された戦略で成功します:ローカルなリアクティビティにはSignals、共有ロジックにはサービス、複雑な調整にはストアを使用します。

Signalsをデフォルトとして始めましょう。共有が必要になったらサービスに移行します。NgRx(従来型またはSignalStore)は、真に複雑な状態の課題のために取っておきます。この実用的なアプローチは、早すぎる最適化を避けながら、コードベースの保守性を保ちます。

最良の状態管理戦略は、実際の問題を解決する最もシンプルなものです。

よくある質問

同じAngularサービスでSignalsとRxJSの両方を使用できますか?

はい、SignalsとRxJSを組み合わせることは推奨されるパターンです。状態の保存にはSignalsを、HTTPリクエストのような非同期操作の処理にはRxJSを使用します。このハイブリッドアプローチは、両方の長所を提供します。

AngularアプリケーションでサービスからNgRxに移行すべきタイミングはいつですか?

複数のフィーチャーにまたがる複雑な状態の相互作用がある場合、タイムトラベルデバッグが必要な場合、またはチームの一貫性のために厳格な状態更新パターンが必要な場合にNgRxを検討してください。サービスの保守が困難になってきたら、それがNgRxを評価する合図です。

Angular SignalsはRxJSを完全に置き換えますか?

いいえ、SignalsはRxJSを置き換えるのではなく補完します。Signalsは同期的な状態管理に優れていますが、RxJSはストリーム、非同期操作、複雑なイベント調整の処理に不可欠です。ほとんどのアプリケーションは両方を使用することで恩恵を受けます。

Signalsと従来の変更検知のパフォーマンス差は何ですか?

Signalsは粒度の細かいリアクティビティを提供し、変更された値に依存するコンポーネントのみを更新します。このターゲットを絞ったアプローチは、頻繁な状態更新や大きなコンポーネントツリーを持つアプリケーションにおいて、ゾーンベースの変更検知を大幅に上回るパフォーマンスを発揮します。

DevTools for the frontend

Gain Debugging Superpowers

Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers.

Star on GitHub12k

We use cookies to improve your experience. By using our site, you accept cookies.