ホームページ  >  記事  >  ウェブフロントエンド  >  Signals を使用して Angular コンポーネントを構造化する方法

Signals を使用して Angular コンポーネントを構造化する方法

Patricia Arquette
Patricia Arquetteオリジナル
2024-10-09 06:23:29586ブラウズ

How I structure my Angular components with Signals

この短い記事では、外部ライブラリを使用せずに信号を使用してコンポーネントを構築する方法を説明したいと思います。もちろん、NgRx のようなものはコードをより堅牢にするために大きな役割を果たしますが、まずはシンプルに始めましょう!

まず最初に、すべての状態をシグナルで定義します。

export class TodoListComponent {
  todos = signal<Todo[]>([]);
}

入力にも同じことが当てはまります。コンポーネントに入力が必要な場合は、Angular の新しい input() 関数を使用してそれを宣言します。これにより、シグナルも得られます。それがルートパラメータである場合は、input.required().

を使用します。

次に、別の状態から導出できる状態を表示したい場合は、常に computed を使用します。

completedTodos = computed(() => this.todos().filter(t => t.completed));

私を知っている方なら、私がクラス メソッド内で直接非同期副作用を実行することをどれほど嫌っているかご存知でしょう...?

export class TodoListComponent {
  todoService = inject(TodoService);

  toggleTodo(id: string) {
    this.todoService.toggle(id).subscribe(newTodo => ...);
  }
}

なぜ聞くのですか?なぜなら、そのメソッドが副作用を直接開始するものである場合 (この場合、subscribe を呼び出します)、バックプレッシャー を制御することができないからです。

バックプレッシャーは次のように要約できます。前の通話が終了していないときにユーザーが ToDo を切り替えたらどうなりますか?

たとえば、次のような問題が数多くあります。

  1. 2 番目の呼び出しを実行する必要がありますか?それとも最初のものが完了するまで待ちますか?それとも最初のものをキャンセルしたほうがいいでしょうか?
  2. 異なるアイテムを短時間で切り替えたらどうなるでしょうか?
  3. ある種の デバウンス または スロットル を導入したい場合はどうすればよいですか?

RxJS を知っていれば (そしてこれを読んでいるなら、もう知っているはずです!)、最初の問題は 4 つの平坦化演算子 (mergeMap、concatMap、switchMap、exhaustMap) を使用して簡単に解決できることがわかります。

RxJS をよく知っている場合は、groupBy と呼ばれる素晴らしい演算子を使用して 2 番目の問題を解決できることがわかります!

しかし、この利点をすべて利用するには、メソッドではなく、Observable ソースが必要です。

科目

サブジェクトを、開いた (完了していない) 空の Observable のように考えてください。これはカスタム イベントを表すのに最適なツールです。

コンポーネント内のすべてのイベントは、サブジェクトによって表すことができます:

export class TodoListComponent {
  ...

  toggleTodo$ = new Subject<string>();
  deleteTodo$ = new Subject<string>();
  addTodo$ = new Subject<void>();
}

その後、テンプレートは必要に応じて、メソッドを呼び出す代わりに単にそれらを呼び出すことができます。

<button (click)="deleteTodo$.next(todo.id)">delete</button>

ソースが Observable になったので、親愛なるオペレーターを使用できます。エフェクトを作成しましょう。

効果

コンポーネントが破棄されたときに takeUntilDestroyed() オペレーターを使用してエフェクトをクリーンアップできるように、コンストラクター内でエフェクトを定義するのが好きです。たとえば、次のようになります。

constructor() {
  this.addTodo$.pipe(
    concatMap(() => this.todoService.add())
    takeUntilDestroyed()
  ).subscribe(newTodo => this.todos.update(todos => [...todos, newTodo]));
}

ここでは、応答の順序を保持するために concatMap を使用し、todo が順番に追加されるようにしています。これは、同時呼び出しがないことを意味します。これは追加操作には最適だと思いますが、他の呼び出しには間違った選択である可能性があります。たとえば、GET リクエストの場合は、ユースケースに応じて、通常、exhaustMap または switchMap を使用する方が良いです。

私は 悲観的更新 と呼ばれるアプローチも使用しています。これは、呼び出しが終了するのを待って内部状態を更新することを意味します。これは個人的な好みです。 todo をすぐに追加し、API 呼び出しがエラーになった場合は catchError を使用して元に戻すことができます。

次に、信号と組み合わせて使用​​することを目的とした Angular の実際のエフェクト関数があります。私はこの関数を同期タスクに使用します。たとえば、URL 内のパラメータが変更された場合 (新しいエンティティ ID を参照する)、新しいエンティティでフォームを更新する必要がある場合があります:

// This comes from the router
id = input.required<string>();

// Always stores the current invoice information
currentInvoice = toSignal(toObservable(this.id).pipe(
  switchMap(id => this.invoiceService.get(id))
));

constructor() {
  effect(() => {
    // Assuming the 2 structures match, every time we browse
    // to a new invoice, the form gets populated
    this.form.patchValue(this.currentInvoice());
  })
}

この手法ではバックプレッシャーを制御できないことに注意してください。この種のことについては問題ありませんが、覚えておいてください。だからこそ、バグのないアプリを作成するには RxJS が依然として必要です。または、この複雑さを内部で抽象化する別のライブラリ。

完全なリアクティブ化が常に良いアイデアであるとは限りません

信号で表す多くの状態は、技術的には派生非同期状態と見なすことができます。たとえば、Todo リストはサーバーからの派生状態とみなすことができます:

// Trigger this when you need to refetch the todos
fetchTodos$ = new Subject<void>();

todos = toSignal(toObservable(this.fetchTodos$).pipe(
  switchMap(id => this.todoService.getAll())
));

このアプローチは、TanStack Query などのライブラリで使用されるアプローチと似ており、新しいデータが必要なときにクエリを手動で無効にします。言い換えれば、ミューテーションごとに常にサーバーにアクセスすることになります。

これはシナリオによっては良い場合もありますが、考慮すべき点が 2 つあります。

  1. 手動での状態更新 (楽観的更新) が困難になります。これは TanStack Query などのライブラリを使用すると簡単になりますが、手動で行うのは面倒です。
  2. これにより、ほとんどの開発者にとってコードを理解するのがやや難しくなります。これは、この種のことに毎日取り組んでいるコンサルタントとして私が見ているものです。

要するに、通常は推奨しません。そして私はいつもと言いました! :)

結論

我希望你喜歡這篇短文!總結一下:

  • 將你的狀態定義為訊號
  • 將派生狀態定義為計算訊號
  • 將非同步效果定義為 Observables
  • 用效果定義您的同步效果

我確信,如果您遵循這些原則,您的應用程式將會更容易維護!

以上がSignals を使用して Angular コンポーネントを構造化する方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。