1. 始める前に
Angular Signals により、使い慣れた Angular に 3 つのリアクティブ プリミティブが導入され、開発が簡素化され、デフォルトでより高速なアプリを構築できるようになります。
作業内容
- Angular Signals で導入された 3 つのリアクティブ プリミティブである
signal()
、computed()
、effect()
について学習します。 - Angular Signals を使用して Angular Cipher ゲームを強化する。暗号はデータの暗号化 / 復号を行うシステムです。このゲームでは、ユーザーは暗号を見つけて解くための手がかりをドラッグ&ドロップしてシークレット メッセージをデコードし、メッセージをカスタマイズして、シークレット メッセージを友だちに送信するための URL を共有できます。
前提条件
- Angular と Typescript に関する知識
- 推奨: Angular Signals ライブラリについては、シグナルでリアクティブ性を再考するをご覧ください
2. コードを取得する
このプロジェクトに必要なものは、すべて Stackblitz です。Stackblitz は、この Codelab で推奨される方法です。または、コードのクローンを作成し、お気に入りの開発環境で開きます。
Stackblitz を開き、アプリを実行します。
まず、任意のウェブブラウザで Stackblitz のリンクを開きます。
- 新しいブラウザタブを開き、https://stackblitz.com/edit/io-signals-codelab-starter?file=src%2Fcrypto%2Fservice.crypto.ts,src%2Fsecret-message%2Fservice.message.ts&service.massage.ts に移動します。
- Stackblitz をフォークして、編集可能な独自のワークスペースを作成します。Stackblitz でアプリが自動的に実行されるため、作業は完了です。
代替方法: リポジトリのクローンを作成してアプリを提供する
VSCode またはローカル IDE を使用するという方法で、この Codelab を実施できます。
- 新しいブラウザタブを開き、https://github.com/angular/codelabs/tree/signals-get-started に移動します。
- リポジトリをフォークしてクローンを作成し、
cd codelabs/
コマンドを使用してリポジトリに移動します。 git checkout signals-get-started
コマンドを使用してスターター コード ブランチをチェックアウトします。- VSCode またはお好みの IDE でコードを開きます。
- サーバーの実行に必要な依存関係をインストールするには、
npm install
コマンドを使用します。 - サーバーを実行するには、
ng serve
コマンドを使用します。 - ブラウザタブを開いて http://localhost:4200 にアクセスします。
3. ベースラインを確立する
出発点は Angular Cipher ですが、まだ機能していません。Angular Signals がゲームの機能を強化します。
最初に、完成後のバージョンの Angular Signs Cypher を完成させます。
- コード化されたメッセージが画面に表示されます。
- キーパッド内の文字ボタンをドラッグ&ドロップして、暗号の解読とシークレット メッセージのデコードを行います。
- 成功したら、メッセージが更新されて、より多くのシークレット メッセージをデコードできるようにします。
- [カスタマイズ] をクリックして送信者とメッセージを変更し、[URL を作成、コピー] をクリックして画面上の値と URL の変更を確認します。
- 参考: URL をコピーして新しいタブに貼り付けるか、友だちと共有し、送信者とメッセージが URL にどのように保存されているかを確認します。
4. 最初の sign() を定義する
シグナルは、変化したときに Angular に通知できる値です。一部のシグナルは直接変更できるものと、他のシグナルの値から値を算出するものがあります。シグナルを組み合わせることで、アプリ内のデータフローをモデル化した依存関係の有向グラフが作成されます。
Angular は、シグナルからの通知を使用して、変更を検出する必要があるコンポーネントを確認したり、定義した効果関数を実行したりします。
superSecretMessage
を signal()
に変換する
superSecretMessage
は、プレーヤーがデコードするシークレット メッセージを定義する MessageService
の値です。現在のところ、この値はアプリに変更を通知しないため、[Customize] ボタンは動作しません。これはシグナルで解決できます。
superSecretMessage
をシグナルにすることで、メッセージがいつ変更されたかをアプリの通知機能で確認できます。ダイアログのメッセージをカスタマイズするときは、シグナルをアプリの他の部分に更新するようにメッセージを設定します。
最初のシグナルを定義するには、各ファイルの TODO(1): Define your first signal()
コメントで次の手順を行います。
service.message.ts
ファイルで、シグナル ライブラリを使用して、superSecretMessage
をリアクティブにします。
src/app/secret-message/service.message.ts
superSecretMessage = signal(
'Angular Signals are in developer preview in v16 today!'
);
これにより、@angular/core
から signal
をインポートするように自動的に求められます。ページを更新すると、以前に superSecretMessage
に問い合わせていたエラーが発生した可能性があります。これは、superSecretMessage
のタイプを string
から SettableSignal<string>
に変更したためです。これを解決するには、Signals API を使用するように superSecretMessage
のすべての参照を変更します。値を読み取る場合は常に、シグナル ゲッター superSecretMessage()
を呼び出します。値を書き込む場合は常に SettableSignal
の .set
API を使用し、メッセージの新しい値を設定します。
secret-message.ts
ファイルとservice.message.ts
ファイルで、superSecretMessage
のすべての参照をsuperSecretMessage()
に更新します。
src/app/secret-message/secret-message.ts
// Before
this.messages.superSecretMessage
this.messages.superSecretMessage = message;
// After
this.messages.superSecretMessage()
this.messages.superSecretMessage.set(message);
src/app/secret-message/service.message.ts
// Before
this.superSecretMessage
// After
this.superSecretMessage()
他の 2 つのシグナルを確認する
- アプリには他に 2 つのシグナルがあります。
src/app/crypto/service.crypto.ts
cipher = signal(this.createNewCipherKey());
decodedCipher = signal<CipherKey[]>([]);
CipherService
は、cipher
シグナルを定義します。これは、アルファベット 1 文字の Key-Value ペアから新しい cipher
文字へのランダムに生成されたマッピングです。これを使って、メッセージをスクランブルし、プレーヤーがキーボードで正常に一致したかどうかを判断します。
また、正常にデコードされた Key-Value ペアの decodedCipher
シグナルもあり、プレーヤーが暗号を解決する際に追加します。
Angular のシグナル ライブラリの設計に備わっているユニークで強力な属性により、どこにでもリアクションを導入できます。シグナルはアプリのサービスで一度定義すれば、テンプレート、コンポーネント、パイプ、その他のサービスなど、アプリケーション コードを記述できる場所すべてで使用できます。コンポーネントのスコープに限定されず、バインドされません。
変更の確認
- アプリを実行する前に、あと 1 ステップ設定が必要です。現時点では、アプリのさまざまな部分に
console.log()
を追加して、新しいsuperSecretMessage
がどのように設定されているかを確認してみましょう。
5. 最初の computed() を定義する
多くの場合、既存の値から状態を導出することがあります。依存値が変更されたときに派生状態を更新することをおすすめします。
computed()
を使用すると、他のシグナルから値を導出するシグナルを宣言的に表現できます。
solvedMessage
を computed()
に変換する
solvedMessage
は、secretMessage
値をエンコードして、decodedCipher
シグナルを使用してデコードします。
別の計算結果から算出されたものがご覧いただけるので、便利です。マッピングされたリアクティブ コンテキスト内のシグナルが変化するたびに、依存関係が通知されます。
現時点では、secretMessage
、decodedCipher
、superSecretMessage
を変更しても solvedMessage
は更新されません。そのため、プレーヤーが暗号を解決しても画面の更新は表示されません。
solvedMessage
を計算にすることで、メッセージの更新や暗号の解決の際に追跡された依存関係から状態の更新を導出できるよう、リアクティブ コンテキストを作成します。
solvedMessage
を computed()
に変換するには、各ファイルの TODO(2): Define your first computed()
コメントで次の手順を行います。
service.message.ts
ファイルで、シグナル ライブラリを使用して、solvedMessage
をリアクティブにします。
src/app/secret-message/service.message.ts
solvedMessage = computed(() =>
this.translateMessage(
this.secretMessage(),
this.cipher.decodedCipher()
)
);
これにより、@angular/core
から computed
をインポートするように自動的に求められます。ページを更新すると、以前に solvedMessage
に問い合わせていたエラーが発生した可能性があります。これは、関数型の superSecretMessage
が string
から Signal<string>
に変更されたためです。これを修正するには、solvedMessage
のすべての参照を solvedMessage()
に変更します。
secret-message.ts
ファイルで、solvedMessage
のすべての参照をsolvedMessage()
に更新します。
src/app/secret-message/secret-message.ts
// Before
<span *ngFor="let char of this.messages.solvedMessage.split(''); index as i;" [class.unsolved]="this.messages.solvedMessage[i] !== this.messages.superSecretMessage()[i]" >{{ char }}</span>
// After
<span *ngFor="let char of this.messages.solvedMessage().split(''); index as i;" [class.unsolved]="this.messages.solvedMessage()[i] !== this.messages.superSecretMessage()[i]" >{{ char }}</span>
superSecretMessage
とは異なり、solvedMessage
は SettableSignal
ではないため、その値を直接変更することはできません。代わりに、いずれかの依存関係シグナル(secretMessage
と decodedCipher
)が更新されるたびにその値が最新の状態に保たれます。
他の 2 つのcomputed()
機能を確認する
- アプリには、他に次の 2 つの計算値があります。
src/app/secret-message/service.message.ts
secretMessage = computed(() =>
this.translateMessage(
this.superSecretMessage(),
this.cipher.cipher()
)
);
src/app/crypto/service.crypto.ts
unsolvedAlphabet = computed(() =>
ALPHABET.filter(
(letter) => !this.decodedCipher().find((guess) => guess.value === letter)
)
);
MessageService
は、計算された secretMessage
(プレーヤーが解決する cipher
でエンコードされた superSecretMessage
)を定義します。
CipherService
は、計算された unsolvedAlphabet
(プレーヤーが解決していないすべての文字のリスト)を定義します。これは、decodedCipher
で解決された暗号鍵のリストから取得されます。
変更の確認
superSecretMessage
がシグナルで、solvedMessage
が計算されたので、アプリは機能します。ゲームの機能をテストします。
LetterGuessComponent
をCipherComponent
のLetterKeyComponent
にドラッグ&ドロップして、暗号の解決とシークレット メッセージのデコードを行います。- より多くのシークレット メッセージをデコードすると、
SecretMessageComponent
がどのように更新されるかを確認できます。 - [カスタマイズ] をクリックして送信者とメッセージを変更し、[URL を作成、コピー] をクリックして画面上の値と URL の変更を確認します。
- 参考: URL をコピーして新しいタブに貼り付けるか、友だちと共有し、送信者とメッセージが URL にどのように保存されているかを確認します。
6. 最初の effect() を追加する
シグナルに新しい値がある場合になんらかの処理が必要になることがあります。effect()
を使用すると、シグナルの変更に応じてハンドラ関数をスケジュールして実行できます。
暗号が解決したら紙吹雪を追加する
アプリが機能したので、暗号が解決され、シークレット メッセージがデコードされた際に紙吹雪を追加して楽しいものにできます。
紙吹雪を追加するには、TODO(3): Add your first effect()
コメントで次の手順を行います。
cipher.ts
ファイルで、メッセージのデコード時に紙吹雪を追加するエフェクトをスケジュール設定します。
src/app/crypto/crypto.ts
import * as confetti from 'canvas-confetti';
ngOnInit(): void {
...
effect(() => {
if (this.messages.superSecretMessage() === this.messages.solvedMessage()) {
var confettiCanvas = document.getElementById('confetti-canvas');
confetti.create()(confettiCanvas, { particleCount: 100 });
}
});
}
この効果がシグナルと計算値(this.messages.superSecretMessage()
と this.messages.solvedMessage()
)にどのように依存するかに注目してください。
効果により、リアクティブ コンテキスト内の紙吹雪関数をスケジュールし、依存関係が更新されたときに追跡して再評価できます。
変更の確認
- 暗号を解いてみます(ヒント: メッセージを短い文に変更すれば、テストを迅速化できます)。紙吹雪のポップが最初の
effect()
を達成しました。
7. 完了
Angular Cipher で、シークレット メッセージをデコードして共有する準備ができました。Angular チームへのメッセージデコードするには、ソーシャル メディアで @Angular のタグを設定してください。🎉
Angular ツールボックスに、3 つの新しいリアクティブ プリミティブが追加されました。これにより、開発を簡素化し、デフォルトで高速なアプリを作成できます。
その他の情報
次の Codelab をご覧ください。
次の資料をご覧ください。
- Angular.io
- シグナルによる再考(Google I/O 2023)
- Angular の新機能(Google I/O 2023)