はじめに
Angularは、Googleとオープンソースコミュニティで開発されているJavaScriptフレームワークです。最初のバージョンはAngularJS(AngularJS 1)と呼ばれていましたが、バージョン2で全面的に刷新され、以降、おおむね半年に1回アップデートされています。
2022年7月の前回記事では、Angularのバージョン14について、変更点や新機能を紹介しましたが、その後Angularは2022年11月にバージョン15、2023年5月にバージョン16がそれぞれリリースされました。本記事では、Angularのバージョン15および16(以下「Angular 15」「Angular 16」と記述)について、主要な変更点や新機能を説明していきます。なお、各バージョンのより詳細な情報や変更点は、Angularの公式ブログ記事(Angular 15、Angular 16)を参照してください。
対象読者
- Angularの最新動向を定期的にチェックしておきたい方
- 既存のAngularソースコードを最新版に追従する必要がある方
- より便利にAngularを活用したい方
必要な環境
Angularの開発では、一般にTypeScript(変換してJavaScriptを生成する、いわゆるAltJS言語)が利用されます。本記事のサンプルコードもTypeScriptで記述しています。
今回は以下の環境で動作を確認しています。基本的にはAngular 16を利用しますが、比較のため一部のサンプルでAngular 14を利用しています。
- Windows 10 64bit版
- Angular 16.0.0、14.0.7
- Angular CLI 16.0.0、14.0.0
- Node.js v18.16.0
- Microsoft Edge 112.0.1722.68
サンプルコードを実行するには、サンプルのフォルダーで「npm install」コマンドを実行してライブラリーをダウンロード後、「ng serve」コマンドを実行して、「https://localhost:4200」をWebブラウザーで表示します。
Angular 15の新機能
以下では、Angular 15の主要な新機能や変更点を紹介していきます。
正式版になったスタンドアロンコンポーネント
モジュール(NgModule)を利用せずにコンポーネントを記述できるスタンドアロンコンポーネントは、Angular 14で開発者プレビューが提供されていましたが、Angular 15で正式版になりました。本記事では、NgModuleを利用した従来のプロジェクトを、スタンドアロンコンポーネントを利用するよう変更する方法を説明します。なお、スタンドアロンコンポーネントの記述方法はAngular 14の時点からほぼ変わらないため、Angular 14の解説記事も参考にしてください。
まず、AngularのWebアプリを起動するmain.tsの処理は、リスト1の通り変更します。従来のアプリ起動処理(1)に代わり、(2)の通りboostrapApplicationメソッドの引数に最初に表示するコンポーネント(ここではAppComponent)を指定して実行します。
// モジュールを利用した従来のアプリ起動 ...(1) // platformBrowserDynamic().bootstrapModule(AppModule) // .catch(err => console.error(err)); // スタンドアロンコンポーネントを利用したアプリ起動 ...(2) bootstrapApplication(AppComponent) .catch(err => console.error(err));
次にAppComponentをリスト2の通り修正して、スタンドアロンコンポーネントにします。@Componentデコレーターで、(1)のstandalone属性をtrueに設定すると、コンポーネントがスタンドアロンコンポーネントになります。コンポーネントで利用する別のコンポーネントやモジュールは、(2)のimports属性に指定します。ここではngIfやngForが定義されているCommonModuleをインポートしています。
@Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'], standalone: true, // スタンドアロンコンポーネントを指定 ...(1) imports: [CommonModule] // CommonModuleをインポート ...(2) }) export class AppComponent { title = 'p001-standalone'; }
リスト1、2の変更後Webアプリを起動すると、Webページの見た目は全く変わりませんが、AppComponentがスタンドアロンコンポーネントとして動作するようになります。
[補足]スタンドアロンコンポーネントの利用をサポートするCLIコマンド
Angular 15の時点では、スタンドアロンコンポーネントを生成するための--standaloneオプションがAngular CLIで利用できました。
ng g component <コンポーネント名> --standalone
さらにAngular 16では、スタンドアロンコンポーネント関連のCLI機能が拡張されました。リスト4のコマンドにより、スタンドアロンコンポーネントを最初から利用するようにプロジェクトを生成できます。
ng new <プロジェクト名> --standalone
また、既存プロジェクトのフォルダーでリスト5のコマンドを実行すると、スタンドアロンコンポーネントを利用するように既存プロジェクトを変換できます。
ng g @angular/core:standalone
複数ディレクティブを合成できるDirective composition API
Angular 15で提供されたDirective composition APIを利用すると、複数のディレクティブを合成して新たなディレクティブにしたり、コンポーネントにディレクティブを合成したりできます。図1のサンプルで利用法を説明します。
このサンプルでは、テキストの色を指定するhlcolorディレクティブと、フォントを指定するhlfontディレクティブを実装します。hlcolorディレクティブの実装はリスト6の通りです。コンストラクターでディレクティブが適用されるHTML要素をelに取得し、CSSのcolor属性を「red」にして、文字を赤色にしています。hlfontディレクティブも同様の実装で、太字、フォントサイズ20ptを指定します(実装はサンプルコードを参照)。
@Directive({ selector: '[appHlcolor]', standalone: true }) export class HlcolorDirective { constructor(private el: ElementRef) { this.el.nativeElement.style.color = 'red'; } }
Directive composition APIでディレクティブを合成するには、リスト7の通り実装します。(1)のhostDirectives属性に指定した複数のディレクティブが合成されます。
@Directive({ selector: '[appHighlight]', standalone: true, hostDirectives: [ // ...(1) HlcolorDirective, HlfontDirective ] }) export class HighlightDirective { }
リスト7のhighlightディレクティブをHTML要素にリスト8の通り適用すると、hlcolorとhlfontの両方の効果が表れ、赤字、太字、フォントサイズ20ptで表示されます。
<p appHighlight>これも大事なことです</p>
hostDirectives属性をコンポーネントで利用すると、複数ディレクティブの効果をコンポーネントに適用できます。リスト9の場合、コンポーネントに含まれる文字列が赤字、太字、フォントサイズ20ptになります。
@Component({ selector: 'app-sub', standalone: true, imports: [CommonModule], templateUrl: './sub.component.html', styleUrls: ['./sub.component.css'], hostDirectives: [ HlcolorDirective, HlfontDirective ] }) export class SubComponent { }
Directive composition APIのより詳細な利用法は公式ドキュメントを参照してください。なお、Directive composition APIは、スタンドアロンのディレクティブのみで利用できます。
画像を最適化するNgOptimizedImage
Webページ上の画像を最適化してページの読み込み速度を上げる、NgOptimizedImageディレクティブが利用できるようになりました。@Componentデコレーターのimports属性にNgOptimizedImageを追加後、テンプレートにリスト10の通り記述して利用します。通常の<img>タグ(1)では画像ファイルのパスをsrc属性に指定しますが、NgOptimizedImage(2)ではngSrc属性に指定します。
<h3>【このページの下部に画像があります】</h3> <p>(略:長い文字列)</p> <h3>通常のimgタグによる画像表示</h3> <img src="assets/image1.jpg" width="320" height="240"> <!--(1)--> <h3>NgOptimizedImageによる画像表示</h3> <img ngSrc="assets/image2.jpg" width="320" height="240"> <!--(2)-->
リスト10のWebページは、長い文字列の下に画像が配置されます。ページ表示後、開発者ツールで確認すると、(1)で参照したimage1.jpgはページ表示と同時にダウンロードされますが、(2)で参照したimage2.jpgはすぐにダウンロードされず、ページを下にスクロールしていく(つまり、画像が見えそうになる)ときに遅延ダウンロードされます(図2)。この処理により、最初にWebページが表示されるまでの時間を短縮できます。
より見やすくなったスタックトレース
Angular 15(正確には14.1)以降では、ChromeやEdgeといったブラウザーの開発者オプション機能と連携して、フレームワーク側のスタックトレースを非表示にして、開発者が自分のコードのスタックトレースをより追いやすいようにしました。この機能を利用するには、まずブラウザーの開発者ツールで設定画面を開き、「Ignore List(無視リスト)」で「リストの無視を有効にする」「既知のサードパーティー スクリプトを~」をチェックします(デフォルトでチェックされています)。
Webページでスタックトレースを出力させると、Angular 14ではフレームワーク側を含めた大量のスタックトレースが出力されます。
一方、Angular 14.1以降では、開発者のソースコードに関連したスタックトレースのみ出力されます。
なお、図4、5に示したスタックトレースを出力するWebページの詳細は、サンプルコードを参照してください。
MDCベースの新しいAngular Materialコンポーネント
Angular Materialのコンポーネントが「Material Components for the Web」(MDC)をベースにリファクタリングされました。この変更は、Angular 13の過去記事で「アクセシビリティ改善を開発中」と紹介しましたが、この開発が完了し、正式版として利用できるようになりました。新旧のラジオボタンを表示する図6のサンプルで利用法を説明します。新しいラジオボタンは間隔が空いていて、タッチ操作時のアクセシビリティに配慮されています。
新旧のAngular Materialコンポーネントを表示するテンプレートの記述は共通で、リスト11の通りです。<mat-radio-group>、<mat-radio-button>コンポーネントを利用します。
<mat-radio-group [(ngModel)]="selectedValue"> <div> <mat-radio-button value="0">Galaxy S23 Ultra</mat-radio-button> </div> (略) </mat-radio-group>
新しいコンポーネントを利用する場合、リスト12の通り、@Componentデコレーターのimportsに、MatRadioModuleを指定します。
import { MatRadioModule } from '@angular/material/radio'; @Component({ imports: [CommonModule, FormsModule, MatRadioModule], (略) })
古いコンポーネントを参照する場合はリスト13の通り、MatLegacyRadioModuleを指定します。
import { MatLegacyRadioModule } from '@angular/material/legacy-radio'; @Component({ imports: [CommonModule, FormsModule, MatLegacyRadioModule], (略) })
[補足]古いコンポーネントへのテーマ適用
リスト13で指定した古いAngular Materialコンポーネントにはデフォルトでテーマが適用されないため、ページ全体のSCSS指定(src/styles.scss)で、古いコンポーネントにもテーマを適用する記述が必要となります。詳細はサンプルコードを参照してください。