はじめに
みなさん、Angular ルネサンスしてますか?
最近、ロゴが大きく刷新されたり、新しい公式ドキュメントページが公開されたりと話題になっている Angular ですが、2023 年は 15.1.0 (2023-01-10) から 17.0.4 (2023-11-20) までアップデートされました。
この記事ではこれらのバージョンからいくつかの機能をピックアップして紹介したいと思います。
これは Angular Advent Calendar 2023 1 日目の記事です。
主な変更点
Angular Signal
今年一番話題になったのはやはり Signal ではないだろうか。新しい reactive system として提供され v17 では API が安定版になっている(一部 developer preview あり)。
Signal には writable と read-only の 2 種類があり、writable な Signal を生成するには次のように signal()
関数を使う。
const count = signal(0);
Signal は getter を使って値を参照でき、set()
を使って更新ができる。
console.log(count()); // 0 count.set(100); console.log(count()); // 100
read-only な Signal は computed()
関数を使って作ることができる。これは別の Signal を元に新たな Signal を算出する関数である。
const doubleCount = computed(() => count() * 2);
たとえば、上記のように宣言することで doubleCount は count が変わったときに更新する必要があることを伝えることができる。
これらの Signal API は angular/core から提供されているため、追加のライブラリなしに使うことが可能である。
effect()
など一部 developer preview の機能も含め、まだまだ改善されることが予定されているため、2024 年も目が離せない。
standalone by default
去年から提供され始めた standalone API だが、ついに v17 では新規にアプリケーションを作成するとデフォルトで standalone ベースで作られるようになった。一応 --standalone
オプションを false にすることで外すことできるが、特にそういうユースケースはないと思う。
また NgModule ベースのアプリケーションを standalone ベースにマイグレーションするコマンドも v16 で提供されている。Angular の機能追加の中でも standalone を前提とした API がどんどん追加されているので、既存のアプリケーションもマイグレーションしていくことが求められている。
細かいマイグレーション手順については過去に紹介したこともあるのでリンクを貼っておく。
Angular アプリケーションを standalone にマイグレーションする - とんかつ時々あんどーなつ
Built-in control flow
v17 で新しい制御フロー構文が developer preview として提供された。これは構造ディレクティブで提供されていた *ngIf
, *ngSwitch
, *ngFor
を置き換えるものである。
それぞれ @if
, @switch
, @for
という構文になる。
@switch
と @case
の間では適切に型の絞り込みがされるようになるため開発者体験が向上するだろう。また @for
は track
が必須になるため、つけ忘れによるパフォーマンス劣化を防ぐことができる。
さらに、NgIf
などの構造ディレクティブが不要になるためバンドルサイズが削減されるなど、書き方だけでなくユーザーへ提供できる価値としても効果がある。
すでにマイグレーションコマンドも用意されているため、さっそく置き換えることもできる。
ng generate @angular/core:control-flow
developer preview ではあるが、Angular チームも安定性には自信を持っていると言っているので積極的に使っていきたい。
Deferrable views
制御フロー構文と同じタイミングで新しく提供されたブロック構文である。これは遅延ロードの新しい仕組みであり @defer
を使って次のように書くことで実現できる。
@defer { <hello-lazy /> }
またロードタイミングの設定も自由度が高く、on viewport
や on idle
, on hover
などトリガーと呼ばれるものを指定するだけで柔軟に設定できる。
@defer (on viewport) { <hello-lazy /> }
こちらもまだ developer preview でありどのくらい安定しているは不明だが、かなり体験が良さそうなので使えるところから使ってみたい。
Vite and esbuild
v17 で新しく @angular-devkit/build-angular:application
という builder が提供された。今までの webpack ベースの @angular-devkit/build-angular:browser
を置き換えるもので Vite, esbuild ベースになっている。
v17 で新規にアプリケーションを作成するとデフォルトで @angular-devkit/build-angular:application
を使うようになっている。既存のアプリケーションからのアップデートでは今のところ自分で angular.json を書き換える必要がある。
これを書き換えるだけで build 時間が短縮されるので、積極的に置き換えていきたい。browser のオプションのうち buildOptimizer
や vendorChunk
などは application では使えないため、置き換えた場合はこれらのオプションは合わせて削除する必要があることだけ注意である。
CLI のマイナーバージョンアップでマイグレーションコマンドが提供されそうなので、この変更が入ったバージョンを待ってからでも遅くはない。
Input value transforms and Required Inputs
ここでは Input プロパティに関する変更を 2 つ紹介する。
1 つ目は Input value transforms である。これは親コンポーネントから受け取った値を自身のプロパティに代入する前に変換用の関数を通すことができる仕組みである。 例えば次の例である。
@Component({ standalone: true, selector: 'my-button', template: `…` }) export class MyButton { @Input() disabled: boolean = false; }
<my-button disabled />
Input プロパティは boolean を期待しているが、文字列の 'false'
や undefined が来てしまうことがある。この差異を次のように書くことで吸収できる仕組みになっている。
@Component({ standalone: true, selector: 'my-button', template: `…` }) export class MyButton { @Input({ transform: booleanAttribute }) disabled: boolean = false; }
色々なことがやりたくなってしまうところではあるが小さい純粋関数を通す、くらいに留めておいたほうがよさそうである。
もう 1 つは Required Inputs である。その名の通り Input プロパティに必須のフラグを付けることができる。
@Component(...) export class MyHeading { @Input({ required: true }) text: string = ''; }
required
が true の Input プロパティはテンプレート側で指定していないとコンパイルエラーになる。
v16 で導入されたため、個人的にはすでに積極的に使っている機能の一つである。実行時ではなくコンパイル時に気付けることが増えるのはとても嬉しい。
takeUntilDestroyed
コンポーネントやディレクティブが破棄されたときに Subscription の購読を解除する必要があるが、この処理についておそらく誰しもが同じようなコードを書いているはずである。これを簡単におこなうための API が @angular/core/rxjs-interop
から提供されている。現時点では developer preview ではあるが、この API に置き換えることで多くのボイラープレートが削除できると思う。
例えば次のように書くことができる。
data$ = this.http.get('...').pipe(takeUntilDestroyed());
デフォルトでは inject context でしか呼び出すことができない。それ以外の場所で呼び出す場合は、明示的に DestroyRef
を渡す必要がある。
readonly #destroyRef = inject(DestroyRef); ngOnInit(): void { this.http.get('...').pipe(takeUntilDestroyed(this.#destroyRef)).subscribe((data) => this.data = data); }
ちなみに inject context 以外で呼び出した場合は NG0203 のエラーが発生するのでその時は DestroyRef
を思い出すとよい。
Jest の実験的サポート
v16 で angular-devkit から Jest 用の builder が提供されている。experimental だが使い方は簡単で builder を @angular-devkit/build-angular:jest
に置き換え、使用できないオプションを消していく。
diff --git a/angular.json b/angular.json index f9f85de..9fe2129 100644 --- a/angular.json +++ b/angular.json @@ -75,14 +75,10 @@ } }, "test": { - "builder": "@angular-devkit/build-angular:karma", + "builder": "@angular-devkit/build-angular:jest", "options": { "polyfills": ["zone.js", "zone.js/testing"], - "tsConfig": "tsconfig.spec.json", - "inlineStyleLanguage": "scss", - "assets": ["src/favicon.ico", "src/assets"], - "styles": ["src/styles.scss"], - "scripts": [] + "tsConfig": "tsconfig.spec.json" } } }
いくつかパッケージのインストールは必要だが、この状態で npm run test
を実行すると足りていないパッケージを教えてくれる。最終的に必要になるのは jest と jest-environment-jsdom になる。
しかしまだ NOTE: The Jest builder is currently EXPERIMENTAL and not ready for production use.
のログが出るため、本格的に使用できる段階ではなさそうである。
Reading the route parameters
パスパラメータやクエリパラメータなどのルーティングパラメータをコンポーネントの Input プロパティとして参照できるようにする機能が v16 で提供されている。
この機能を使うには withComponentInputBinding()
を有効にする必要がある。設定方法は provideRoute()
の第 2 引数に渡すだけである。
bootstrapApplication(AppComponent, { providers: [provideRouter(routes, withComponentInputBinding())], })
この設定をすることで次のようなルーティングがあったときに id を Input プロパティとして受け取ることができる。
// routes.ts const routes: Routes = [ .... { path: 'detail/:id', component: HeroDetailComponent }, ]; // hero-detail.component.ts @Component({...}) export class HeroDetailComponent implements OnInit { @Input() id!: string; }
これによりルーティングパラメータを取得するために ActivatedRoute を使う必要がなくなる。
アプリケーションが大きくなっていき、コンポーネントの中でも責務を分割していくと ActivatedRoute を inject できるのは routed component だけというルールを作ることもあった。しかし withComponentInputBinding
を有効にすることでこのルールが強制されるため、個人的には積極的に置き換えていきたいと思っている。
まとめ
15.1.0 から 17.0.4 までの更新で記憶に残っているもの、積極的に使っていきたいものを中心にピックアップしてみました。
その他 SSR / SSG 周りも大きく改善された 1 年でしたが、今回は取り上げていないのでこのあとの Advent Calendar で誰かが書く記事を待ちたいと思います(笑)
また、今年の変更の多くは Angular 日本ユーザー会が配信している ng-japan OnAir でも触れられているので、下記リンクの動画を見ることで deep dive できるかもしれません。 www.youtube.com
明日は carimatics さんです!