Angularの新しい未来：Signalsを実際に使ってみよう

ハイサイ！オースティンやいびーん。んな、がんじゅー やいびーみ？

# 概要

Angular 16のリリースに含まれる予定の新原始型`Signal`を使っていくつかの具体的な使用例を紹介します！

## `Signal`とは？

Angularのフレームワークが変更をパフォーマンスよく検知するための新しい**Reactive Primitive**です。

SolidJSのsignalと概念が一緒で、非常に単純です。Signalには以下の二つの特徴があります。

1. 値を保持している 
1. 値が変わった時に信号を出す能力を持っている

上記の二つの特徴をAngularで使うと、同期的な処理を簡単にテンプレートに反映できます。

SignalとAngularの変更について詳しくは以下の記事をご覧ください。

https://qiita.com/tronicboy/items/fd28ee5799fe968a6317

# 試験的にSignal APIを使う方法

Signalを今でも使ってみたい！という方、実はもうすでにPOCとしてAngularチームが実装して出してくれています！

使うためには`@angular/cli@16.0.0`を使う必要があります。

まだ試験的にバージョンがよく変わるので、npmで今現在の最新のバージョンを確認してください。

https://www.npmjs.com/package/@angular/cli?activeTab=versions

上記のnpmページから最新のバージョンを取得したら以下のコマンドに代入して実行すればAngular 16のプロジェクトを作ることができます。

```
npx -p @angular/cli@16.0.0-next.6 ng new signals-play
```

そうすると`ng serve`を実行してAngularのアプリが起動するか確認してください。
![Screenshot 2023-04-03 at 8.59.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2344956/0806c908-e1c1-24f6-d4f9-f2d3d9e03423.png)

# Signalを部品とテンプレートで使う

`app.component.ts`で`signal`をインポートしてみましょう。

```typescript
import { Component, signal } from '@angular/core';
```

補足説明が書いてありますが、`@developerPreview`になっています。

![Screenshot 2023-04-03 at 9.01.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2344956/5805ceaf-4cec-3682-36a9-4f3b786e0e46.png)

以下のように初期値を代入すると型を推測してくれます。

```typescript
import { Component, signal } from '@angular/core';

@Component({
...
})
export class AppComponent {
  clickCount = signal(0);

  addClick() {
    this.clickCount.update((clicks) => clicks + 1);
    // OR
    this.clickCount.set(this.clickCount() + 1);
  }
}
```

テンプレートで以下のように現在の値を表示できます。テンプレートでは`clickCount`を関数として呼び、現在の値を取得します。

```html:app.component.html
<h1>Count: </h1>
<p>{{ this.clickCount() }}</p>

<button (click)="this.addClick()" type="button">Add 2</button>
```

「テンプレートで関数を呼ぶのは危険」と経験上ご存知の方もいらっしゃるかと思いますが、Signalは副作用を起こさないので心配無用です。

以下のような結果になります。

![ezgif.com-video-to-gif.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2344956/aed598fd-fcb7-4a4e-4fd3-af1988e5db21.gif)

非常に簡単です。

# computedの使い方

Signalは上記の説明の通り、値を持ってそれが変わった時にその変化を知らせるという二つの役割しかないのです。

ただ、同期処理の多くは、値と値を使って何かを計算することがほとんどです。

signal同士を使って計算する時は、`computed`という関数を使います！

以下、ページボタンの例で紹介します。

```typescript:app.component.ts

import { Component, signal, computed } from '@angular/core';

@Component({
...
})
export class AppComponent {
  private itemCount = signal(124);
  private itemsPerPage = signal(10);
  currentPage = signal(0);
  pageCount = computed(() => Math.ceil(this.itemCount() / this.itemsPerPage()));
  canGoForward = computed(() => this.currentPage() < this.pageCount());
  canGoBack = computed(() => this.currentPage() !== 0);

  goForward() {
    if (!this.canGoForward()) return;
    this.currentPage.update((pageNo) => pageNo + 1);
  }

  goBack() {
    if (!this.canGoBack()) return;
    this.currentPage.update((pageNo) => pageNo - 1);
  }
}
```

テンプレートでは以下のようにボタンの属性にも上記のロジックを適応させます。

```html:app.component.html
<h1>Current Page</h1>
<p>{{ this.currentPage() }}</p>
<nav>
  <button (click)="this.goBack()" type="button" [disabled]="!this.canGoBack()">Back</button>
  <button (click)="this.goForward()" type="button" [disabled]="!this.canGoForward()">Next</button>
</nav>
```

## 結果

![ezgif.com-video-to-gif (1).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2344956/e5cb1c69-6189-4f24-e945-670651cc2030.gif)

とても簡単で素晴らしい！

# まとめ

徐々にAngularのSignalが具体性を高めてきていますが、ワクワクが絶えません。

やっぱり上記のような同期処理を各ロジックが非常に簡単になりそうなので助かります。

また、Vueにも似たような文法なので、筆者は受け入れやすい感じです。

みんなもぜひ試してみてください！