はじめに
Angularは、Googleとオープンソースコミュニティで開発されているJavaScriptフレームワークです。最初のバージョンはAngularJS(AngularJS 1)と呼ばれていましたが、バージョン2で全面的に刷新され、以降、おおむね半年に1回アップデートされています。
2022年4月の前回記事では、2021年5月リリースのバージョン12と、同11月リリースのバージョン13について紹介しました。今回紹介するバージョン14(Angular 14)は、バージョン13から約半年後の2022年6月に、これまで通りのサイクルでリリースされました。本記事では、Angular 14で導入された主要な変更点や新機能を説明していきます。なお、全ての変更内容については、公式ドキュメントを参考にしてください。
対象読者
- Angularの最新動向を定期的にチェックしておきたい方
- 既存のAngularソースコードを最新版に追従する必要がある方
- より便利にAngularを活用したい方
必要な環境
Angularの開発では、一般にTypeScript(変換してJavaScriptを生成する、いわゆるAltJSの一種)が利用されます。本記事のサンプルコードもTypeScriptで記述しています。
今回は以下の環境で動作を確認しています。
- Windows 10 64bit版
- Angular 14.0.4
- Angular CLI 14.0.4
- Node.js v16.15.1
- Microsoft Edge 103.0.1264.44
サンプルコードを実行するには、サンプルのフォルダーで「npm install」コマンドを実行してライブラリーをダウンロード後、「ng serve」コマンドを実行して、「https://localhost:4200」をWebブラウザーで表示します。
コンポーネントをモジュールから解放するスタンドアロンコンポーネント
これまでAngularのコンポーネントは、モジュール(NgModule)の一部として扱われ、インポートもモジュール単位で行ってきました。Angular 14で導入された「スタンドアロンコンポーネント」では、モジュールを利用せずにコンポーネントを単体で実装できるようになります。なおAngular 14のスタンドアロンコンポーネントは「開発者プレビュー」なので、正式版では記述方法などが変更される可能性があります。
スタンドアロンコンポーネントの利用法
図1のサンプルで、スタンドアロンコンポーネントの利用法を説明します。このサンプルは画面全体のAppComponent、リストの1行に対応するPhoneComponent、各行下部ボタンに対応するActionsComponentの3種のコンポーネントから構成されます。また、リストやボタンにはAngular MaterialのCard、Buttonコンポーネントを利用します。
AppComponentでは、リスト1の<app-phone>でPhoneComponentを複数利用します。PhoneComponentにはname(名前)とvendor(メーカー)を指定します。
<app-phone name="Galaxy S22" vendor="Samsung"></app-phone> <app-phone name="Reno7 A" vendor="OPPO"></app-phone> <app-phone name="POCO F4 GT" vendor="Xiaomi"></app-phone>
リスト1で利用されるPhoneComponentの実装はリスト2の通りです。
@Component({ selector: 'app-phone', standalone: true, // スタンドアロンコンポーネントにする ...(1) imports: [ // コンポーネントへのインポート指定 ...(2) MatCardModule, // 使用するモジュール ...(2a) ActionsComponent // 使用する別のスタンドアロンコンポーネント ...(2b) ], templateUrl: './phone.component.html', styleUrls: ['./phone.component.css'] }) export class PhoneComponent { // コンポーネントへ渡される引数 ...(3) @Input() name = ''; @Input() vendor = ''; }
スタンドアロンコンポーネントにするには、(1)のstandaloneをtrueにします。また、従来モジュールで行っていたインポートの指定を、コンポーネントのimports(2)に記述します。このimportsには、使用するモジュールや別のスタンドアロンコンポーネントを指定します。ここでは(2a)でCardを含むAngular Materialのモジュール、(2b)でコンポーネントに含める別のスタンドアロンコンポーネントを指定しています。なお(3)はコンポーネントに渡される変数で、リスト1のname、vendor属性に対応します。
(2b)で指定されているActionsComponent(actions.component.ts)も、リスト2と同様の記述でスタンドアロンコンポーネントにしています。詳細はサンプルコードを参照してください。
最上位のコンポーネント(ルートコンポーネント)をスタンドアロンコンポーネントにすることもできます。standalone属性をtrueに設定してAppComponent(app.component.ts)をスタンドアロンコンポーネントにした後、起動処理を記述するmain.tsファイルを、リスト3の通り修正します。
// モジュールを起動する従来の処理 ...(1) // platformBrowserDynamic().bootstrapModule(AppModule) // .catch(err => console.error(err)); // スタンドアロンコンポーネントを起動する処理 ...(2) bootstrapApplication(AppComponent) .catch(err => console.error(err));
従来は(1)の通り、モジュール(AppModule)を指定して起動していましたが、ルートコンポーネントがスタンドアロンコンポーネントの場合は(2)の通り、bootstrapApplicationメソッドにルートコンポーネントを指定して実行します。
フォーム記述を楽にする、型付きリアクティブフォーム
スタンドアロンコンポーネントと並ぶAngular 14のトピックとして、「リアクティブフォームで型情報が有効になった」ことがあげられます。以下ではリアクティブフォームを大まかに説明した後で、型付きリアクティブフォームのメリットを紹介していきます。
Angularの「テンプレート駆動フォーム」と「リアクティブフォーム」
Angularでは、「テンプレート駆動フォーム」と「リアクティブフォーム」の2種類のフォームが利用できます。テンプレート駆動フォームは、テンプレートに記述したフォームコントロールに[(ngModel)]ディレクティブを設定して、フォームの値をコンポーネントのプロパティに紐づけます(過去記事も参照してください)。コントロールの個数が少ない小規模なフォームに向いています。
一方でリアクティブフォームでは、フォームとその構成要素を表すモデルを直接コンポーネントに記述して、テンプレート上のフォームと紐づけます。フォーム値の変更に反応して(リアクティブに)フォーム全体の値を受け取ったり、モデルを操作してフォームコントロールを動的に追加したりできます。コントロールの個数が多く複雑なフォームに向いています。
リアクティブフォームの例
リアクティブフォームでコンポーネントにフォームモデルを記述するには、フォーム内のコントロール、そのグループ・配列を、FormControl、FormGroup、FormArrayに紐づけて記述します。図2のサンプルで説明します。
図2のフォームは、リスト4の構造を持っています。
<form [formGroup]="form"> <!--(1)--> (略) <div> <input id="last-name" type="text" formControlName="lastName"> <!--(2)--> </div> (略) <div formGroupName="address"> <!--(3)--> <div> <input id="pref" type="text" formControlName="pref"> </div> (略) </div> (略) <div formArrayName="aliases"> <!--(4)--> <div *ngFor="let alias of aliases.controls; let i=index"> <input id="alias-{{ i }}" type="text" [formControlName]="i"> </div> </div> (略) </form>
(1)の[formGroup]で、後述するリアクティブフォームの変数formを割り当てます。(2)ではformControlNameでフォームコントロール名を、(3)ではformGroupName属性でフォームグループ名を、(4)ではformArrayNameでフォーム配列名を割り当てています。
リスト4に対応するリアクティブフォームの実装は、FormControl、FormGroup、FormArrayクラスのオブジェクトを利用してリスト5の通り行います。リスト4とリスト5は相似の階層構造を持っており、(1)~(4)がそれぞれ対応します。
form = new FormGroup({ // ...(1) lastName: new FormControl(''), // ...(2) (略) address: new FormGroup({ // ...(3) pref: new FormControl(''), (略) }), aliases: new FormArray( // ...(4) [new FormControl('')] ) });
リスト4、5の通りリアクティブフォームの変数を定義してテンプレートのフォームと紐づけると、リスト6の通り、フォームやその内容を操作できるようになります。
// aliasesを取得するGetter ...(1) get aliases() { return this.form.get('aliases') as FormArray; } // aliasesにフォームを追加 ...(2) addAlias() { this.aliases.push(new FormControl('')); } // フォームに値を設定 ...(3) updateProfile() { this.form.patchValue({ lastName: '吉川', firstName: '英一', (略) }); }
(1)はフォームに含まれるaliasesをFormArrayとして取得するGetterメソッドです。これを利用して(2)では、aliasesに新たなFormControlを追加して、フォームにテキストボックスを動的に増やしています。また(3)では、form.patchValueメソッドを利用して、フォーム内の複数コントロールに一括で値を設定しています。
型付きリアクティブフォームのメリット
リアクティブフォームで型情報が有効になったAngular 14では、リアクティブフォームの要素や型を、開発環境やコンパイラが認識します。例えばリスト6(3)の処理を記述する際に、要素の候補を表示できます。また、文字列が入るフォームに数字を代入しようとするとエラーが表示されます(図3)。
なお、型情報がないリアクティブフォームを実装するには、UntypedFormGroup、UntypedFormControl、UntypedFormArrayクラスを利用します。サンプルコードに含まれるp002a-reactive-form-untypedサンプルでは、これらを利用して図2と同じフォームを実装しているので参考にしてください。このサンプルでは、図3に示した候補の表示や型不一致エラーの検出が行われません。