2019/12/04 追記
@okunokentaro さんが熱いアンサー記事を書いてくれました。
-
Angularでの開発を快適に進めるために知っておきたいこと
https://qiita.com/okunokentaro/items/503ab7a4c7601b564de0
https://gist.github.com/okunokentaro/dc4cd470999fd90aba73423d09a37485
本当に本当にありがたいです。この短時間でこんな良質の記事が書けるのは本当にスゴイ。 Angular コミュニティの暖かさと熱さを感じる 1 日でした。この記事もぜひ参考にしてください。
(ただ、「Angular は全員 80 点」という言説はまるで誰が書いても同じようなコードになるかのような印象を与えるという意味で明らかに語弊があります。実際には書くひとによって、 RxJS の使い方はもちろん、モジュールの区切りも、サービス、ディレクティブ、パイプなど各機能の使い方も、フォームの書き方ひとつ取ったって、何もかも違ってくるのですが...。)
Angular #2 Advent Calendar 2019 X 日目の記事です。 界隈の方々にご迷惑がかかりそうでしたのでアドベントカレンダーへの参加は取りやめました。特にカレンダーを作成してくださった @lacolaco さんごめんなさい。
私の Qiita 初投稿記事です。
Angular と React (と Vue.js)を比較する記事は検索すればいくらでも出てきますが、「いやいやそこじゃないんだよ」と感じる記事ばかりです。誰かの受け売りで「Angular は大規模開発に向いてる」みたいな適当なことを書くのは本当にやめてほしい。
そんなわけで、これからフロントエンドフレームワークを選ぶ方へ向けて、 Angular の問題点と、それでも Angular を選ぶ理由についてまとめてみました。
(1) 6ヶ月ごとにメジャーバージョンアップ
普通に考えましょう。これは正気の沙汰ではありません。
Angular 開発者の気持ちになってみよう
- 設計ミスしても短期間で取り消せる♪
- 実験的な機能も入れちゃえる♪
ライブラリ開発者の気持ちになってみよう
- サポート続けるのツライ...
- メンテナンスに追われるぐらいならライブラリなんか開発しない方がいいんじゃないか...
アプリ開発者の気持ちになってみよう
- Angular を更新してたら機能追加できない
- 機能追加してたら Angular を更新できない
- Angular 更新作業中も他のメンバーは機能追加しなきゃいけなくてカオスに...
- サードパーティ製ライブラリを安心して使えない
- ライブラリが Angular の新しいバージョンに対応するまで Angular のバージョンを上げられない(そもそもライブラリ作者は新しいバージョンに対応する気もないかもしれない)
- アプリが Angular の新しいバージョンに対応する前にライブラリがサポート対象の Angular バージョンを引き上げるとライブラリのバージョンを上げられない
- なるべくサードパーティ製ライブラリを避けて自製するハメに...
まとめると
- フレームワーク開発者は嬉しい
- エコシステムは育たない
- 古いバージョンを使い続ける覚悟が必要
Angular Update Guide は大いに役立ちますが、これがあるから OK ということにはなりません。
実際、私の知人がプロダクトで Angular 2 を使い続けているそうです。もう Angular 9 が出るのに...。
React は unstable_
プレフィックス付きの機能を除けば 16.0.0 (2017 年 9 月リリース) から 2 年以上 Breaking Changes がありません。新機能(たとえば Hooks は 16.8 以降)を使いたい、でも互換性の問題でバージョンは上げられない、みたいなことにはならないんです。
(2) コンポーネント定義の苦痛
Angular でコンポーネントを書くのは苦痛です。
冗長なコンポーネント定義
たかだかコンポーネントをひとつ定義するのに仰々しいデコレーター付きのクラスを記述しなければならず、さらには NgModule
にも宣言が必要になり、あまりにも冗長です。
しかも、**コンポーネント名がファイル名とクラス名とセレクターとでちょっとずつ違う形で現れます。**これは本当に最悪と言わざるを得ません。コンポーネントの名前を変えようと思ったら、ファイル名を変え、クラス名を変え、セレクターを変え、さらには NgModule
でのインポート文と宣言も変えなければなりません。多少はエディターが手伝ってくれるものの、これは日々のストレスです。(名前変更については CLI の Issue がありますが長らく放置されているようです。 Rename / remove / move components · Issue #900 · angular/angular-cli)
もちろん、名前変更がツールでできるようになればそれでいいという話ではありません。同じ言葉が(しかもちょっとずつ違う形で)あちこちに現れること、不必要に冗長であることが問題なのです。
(実質的に) ひとつのファイルに複数のコンポーネントを書けない
これが意外に大きな問題で、開発者体験を著しく落としていると私は思います。(ちなみに Vue.js の単一ファイルコンポーネントを書いているときにも同じことを感じました。)
React や lit-html を使って開発していれば、 export
するコンポーネントのインターフェイスを変えることなく、ファイル内でコンポーネントを分割したり統合したりしてリファクタリングできます。コンポーネントで利用する変数やスタイルのスコープを狭くするためにも、コンポーネントの粒度を小さく保つことはとても重要です。
Angular ではこのようなリファクタリングが非常にやりづらくなっています。
- 公式のスタイルガイドに「常に従うべきもの」として「サービスやコンポーネントなどは1ファイルにつき1つだけの定義としてください。」というガイドラインがある(Angular スタイルガイド Style 01-01)
- ファイル内でしか使えないコンポーネントを作るためには、そのファイル内に
NgModule
も合わせて記述しなければならない(コンポーネントの公開範囲が TypeScript のモジュール単位ではなくNgModule
単位であるため) - そもそもコンポーネント定義コードが冗長すぎて、ひとつ定義するだけでファイルを区切るだけの大きさになる
この制約により、 Angular ではコンポーネントを分割する動機付けが弱まり、個々のコンポーネントが大きくなりがちです。コンポーネントが大きくなると、さらにメンテナンスしづらくなり、分割しづらくなり、もっと大きくなっていきます。
Angular でコンポーネントを書くのは苦痛です。
ひとつのコンポーネント定義に 3 ファイル?
ひとつのファイルに複数のコンポーネントを書けないどころか、 Angular のデフォルトではひとつのコンポーネント定義が 3 ファイルになります(HTML テンプレート、スタイルシート、スクリプト)。ことあるごとに 3 ファイル(テストを合わせると 4 ファイル)を駆け回るのは発狂するほど面倒です。
もちろんこんな馬鹿げたデフォルトを受け入れる必要はありません。**もし Angular を利用するなら、単一ファイルコンポーネントを使いましょう。**コードの見通しがよくなります。たとえば HTML 要素と適用されるスタイルとの行き来がファイル内検索で済みますし、キーワード出現箇所の複数選択(マルチカーソル)で簡単に置換できます。 VS Code なら拡張機能 angular2-inline を入れればシンタックスハイライトやコード補完も効きます。もし単一ファイルコンポーネントにすることでファイルが大きくなりすぎると感じるなら、おそらくそのコンポーネントは責任を負いすぎています。 HTML テンプレート・スタイルシート・スクリプトを別ファイルに分離するより、コンポーネント自体の分割を検討するべきでしょう。
(3) HTML テンプレート構文
Angular はたとえば <ng-container *ngFor="let item of items; index as i">
のような独自の HTML テンプレート構文を持ちます。
React との比較としては次の表現が私にはしっくり来ます。
- Angular — Brings JavaScript into HTML
- React — brings HTML into JavaScript
Another way to to look at this is Angular enables programming in HTML and JSX lets you emit HTML from JavaScript.
Angular and React - Scott Larribeau - Medium
https://medium.com/@slarribeau/angular-and-react-7004d2484d4d
Angular では独自の HTML テンプレート構文の世界でスクリプトを記述することになります。 React のコードは JavaScript の世界で完結します。
結果として Angular には次のようなデメリットがあります。
- ECMAScript が進歩してもメリットを享受できない
- Optional Chaining, Partial Application, Wavy Dot, ...
- きっと今後も機能が増えていく...
- Babel プラグインなどによる機能拡張もできない
- 独自構文分の学習コストがかかる
- (JSX も学習コストはかかるが Angular のそれは比じゃない)
- きっと今後も独自機能が増えていく...
(ちなみに Optional Chaining は使えたりしますが Angular 8 現在array?.[0]
が使えないなど不完全な実装になっています)
Angular でコンポーネントを書くのは苦痛です。
(4) DI 機構のせいでテストがしょっちゅう意味もなく壊れる
たとえばコンポーネントが依存する「サービス」が増えるだけでコンポーネントのテストが失敗するようになります。コンポーネントの挙動が変わらないとしてもです。日々のストレスです。
DI コンテナーがテストを書きやすくしてくれる、みたいな言説がありますが、本当でしょうか? Jest や Sinon.JS を使って素直にモックする書き方に比べると、 DI コンテナーのためのおまじないが増えるだけのように思います。
(Angular のユニットテストあるあるについてはちょうど昨日の @ringtail003 さんの記事『Angular8 ユニットテストが動かねぇ!』が参考になるかもしれません。)
そもそも依存関係の逆転に DI コンテナーは全く不要です。 DI コンテナーの利用は要らぬ複雑性と不透明性を招き入れます。
そして DI コンテナーによる DI は汚染的です。たとえば Angular の HttpClient
クラスのインスタンスは DI コンテナーを通じて提供されます。 axios のようなライブラリと異なり、 import
しただけでは使えません(これだけでも単に不便です)。そして HttpClient
を利用するサービスはモジュール全体の依存関係グラフに参加することになります。そのサービスを利用する別のサービスにも DI コンテナーの使用(モジュール全体の依存関係グラフへの参加)を強制するということです。
なにが悪いの? と思われるかもしれません。テストのおまじないが増え、ロジックの可搬性がなくなり、リファクタリングしづらくなり、もっと言えば DI コンテナー自体のバグに振り回される可能性だってあるのです。 DI コンテナーに潜むメモリリークのようなバグは非常に発見しづらい上に致命的になりがちです。(ちなみに Java EE の CDI の参照実装 Weld は Glassfish 4.0 に組み込まれたバージョンにおいて致命的なバグが複数ありました... DI コンテナー怖い...)
DI コンテナーを愛する方々がいることは認識しています。私のように DI コンテナーに嫌悪感がある人間もいます。 DI コンテナーの使用が任意なら構わないのです。 Angular の API は基本的に DI コンテナーを介して提供されるため、 DI コンテナーの使用が半ば強制的であることが問題なのです。
(皮肉なことに、 DI コンテナーへの依存の汚染を食い止める真っ当な方法は依存関係の逆転です。いや皮肉でもなんでもないか。)
(5) 古いバージョンの TypeScript を使わされる
@angular/cli 8.3.20 現在、まだ TypeScript 3.5 系を使ってろと言われます。 3.7.2 が出てから 1 ヶ月が経とうとしているのに...。
Angular 自体の最新化が難しい場合は遥かに古いバージョンの TypeScript を使い続けることになるかもしれません。
一応 disableTypeScriptVersionCheck
というフラグで制御はできますが...
(6) RxJS
RxJS は悪くないだろ、というひともいます。私は RxJS は悪いと思います。
RxJS を駆使して書かれたコードは読めない
RxJS でコード書くの楽しい!
「なにこれパズルみたい♪」
「自分で途中の状態を持っとかなくていいじゃん!」
「他にはどんなオペレーターがあるのかな?(知識の隙間)」
RxJS というおもちゃを手に入れると、目に映るものすべてがストリームに見えてくるものです。
RxJS で他人が書いたコード読むの厳しい...
「なにこれパズルみたい...💀」
「このコードはいったい何がしたいんだ...??」
「mergeMap()
ってなんだよ... なんだflatMap()
か紛らわしい名前だな... いやここexhaustMap()
でいいんじゃないか...?」
「shareReplay(1)
呼んでないじゃん このmap()
何万回呼ばれるんだよ...」
私は前任者が RxJS を駆使して書いたコードをピュアな TypeScript コードに書き換えていくという楽しいリファクタリングをしたことがあります。 RxJS を駆使したコードのまま機能追加するのでは品質に責任が持てないと感じたからです(あと後で読みにくいだろうなと)。あなたにとっては RxJS なんて取るに足らない簡単なものかもしれませんが、私のように残念な担当者へ引き継ぐ可能性はありませんか?
RxJS を使いこなせない私にマウントを取るのはヤメテ!
RxJS は間違えやすく、間違いに気付きにくい
- 購読解除(
unsubscribe()
orcomplete()
)漏れ-
takeUntil()
で解除したつもりがswitchMap()
で漏れたり
-
- 裏で API を呼びつつ重い処理してるつもりが
subscribe()
するまで呼べてなかった -
combineLatest()
とwithLatestFrom()
との取り違え -
distinctUntilChanged()
漏れ -
shareRelay(1)
漏れ -
combineLatest()
のグリッチによるチラつき- 共通祖先を持つ複数のストリームを結合してストリームを生成すると、イベントの伝搬順序によっては一時的に不正な(意図しない)状態が発生する場合があります。このような現象をグリッチと呼ぶそうです。 RxJS ではグリッチが発生します(発生しないよう気を付ける必要があります)が、 flyd など対策済みのストリームライブラリも存在するようです。グリッチ対策にはランタイムコストがかかるようですので対策すべきとも言い切れません。
- https://en.wikipedia.org/wiki/Reactive_programming#Glitches
- https://github.com/paldepind/flyd#atomic-updates
たぶん RxJS 上級者ならもっともっといくらでも間違えやすいコードを思いつくだろうと想像します。厄介なのは、サラッと動かしたときには正しく動いてしまい、バグやパフォーマンス問題の発見が遅れることです。
学習コストが(とんでもなく)高い
オペレーターだけでも 100 個以上あるそうです。
数が多いだけでなく、ひとつひとつの習得にも時間がかかります(私の頭が弱いだけという説もあります)。
また、 scan()
、 publish()
、 mergeMap()
など API の名称があまりに汎用的で、挙動が連想しづらく、覚えられません。覚えても次に見るときには忘れています(私の頭が弱いだけという説もあります)。まず Hot と Cold というモード名からして...
コードの多様化
書くひとによってクセも品質もバラバラなコードが生まれやすい土壌になります。
リアクティブプログラミングの問題? RxJS 固有の問題?
ここまでに挙げてきた RxJS の問題は、リアクティブプログラミングの一般的な問題というよりは RxJS 固有の問題だろうと私は考えています。
(初めて .NET の Reactive Extensions に出会った頃は、 LINQ と同じインターフェイスで断続的な時系列イベントを扱えるなんて、と感動したものですが、 RxJS は...)
どうして Cycle.js の作者はわざわざストリームライブラリを再発明する必要があったのでしょう?
Andre Staltz - Why we built xstream
https://staltz.com/why-we-built-xstream.html
それでも Angular を選ぶ理由
それは 複数のライブラリを選んで組み合わせる方が大変そうだから です。
「DOM 構築は React、 CSS-in-JS は Linaria、 axios 要るかな、 TypeScript で型チェックして Babel でトランスパイルしよう、モジュールバンドラーは Rollup かな、 ESLint でチェックしよう、ユニットテストは Jest でしょ、状態管理に Redux 要るかな? Mobx 使うぐらいなら Vue.js でいいような... やっぱ Vue.js にしようか、でも...」
たくさん調べて、たくさん試して、たくさん悩んで、やっとのことで選び抜いたライブラリ群。それでもあとで後悔しそうな気がしてしまいます。ここで問題なのは環境構築の大変さというより決断の大変さです。
Angular を選んでしまえば、あとは NgRx を使うか使わないか決めるぐらいでしょう。
また、たくさんのライブラリを組み合わせるとなれば、個々のライブラリにバージョンがあるわけですから、半年もあれば 1 つや 2 つメジャーバージョンアップがあるかもしれません。バージョンアップの周期も読みづらくなるでしょう。(ただし、個々のライブラリのバージョンアップの影響はとても小さくなるはずですが。)
じゃあ Angular を選ぶ?
私は Angular を選びません。
まず、小さな専任ライブラリの集合にしておけば、いざとなったとき部分交換できるという保険がつきます。
たとえば、もし React で作ってみて何か問題が起きたなら Preact や Inferno を試せます。 lit-html に問題があれば hyperHTML や lighterhtml で代替できるかもしれません。 Linaria が合わなければ emotion や styled-components が使えるかもしれません。本当にいざとなれば専任の小さなライブラリぐらいなら自作もできるでしょう。
おそらく Angular を選ぶと取り返しはつきません。 Angular には Angular という決断の問題があるのです。
それから私がライブラリ選定で最も悩むポイントは実は状態管理です。 Angular を選んでも NgRx を使うかどうかという最も悩ましい決断ポイントが残ります。
なにより Angular でコンポーネントを書くのは苦痛です。テストが壊れるのはさらに苦痛です。 JSON 色付け係としての日常生活が不幸になります。
それでもまだ Angular を選ぶ理由
Google を信頼している
気持ちは分かりますけど Google っていろんなことサラッとやめる印象ありませんか?
Google Graveyard - Killed by Google
https://killedbygoogle.com
そういえば高分子みたいなライブラリが昔あったような...(lit-html 好きですよ私)
でも AngularJS は相当がんばってサポートしているように見えますね。 AngularJS の反省が生かされている Angular はもしかしたら本命なのかもしれません。 Google への信頼でこれまで挙げた問題に目を瞑れるなら Angular は選択肢になるのでしょう。
開発メンバーに Angular の知見があり、フロントエンドに関する他の知見がない
この状況なら Angular が無難でしょうね。
AngularJS からの移行
どう考えても別物ですけど、もしかしたら React 等と比べると移行しやすいのでしょうか...?
すみません、私に知見がありません。
大規模開発に向いていると聞いた
それは嘘です。長くなるので節を切りました。
Angular は大規模開発に向いている?
そのようなことを述べている記事を見かけますが、根拠は全く述べられていないか、 Google の権威のみに依拠しているか、 Angular である必要がないか、単に間違っています。
「Angular 大規模」でググった結果をいくつか見てみました。
Angularはなぜ大規模アプリケーション開発に適しているのか。そしてAngular対応の業務用UIコンポーネント「Wijmo」のメリットとは[PR] - Publickey
https://www.publickey1.jp/blog/17/angularangularuiwijmopr.html
「Angularが大規模開発に適している理由とは?」 というそのものズバリな節がありますが、内容は薄いです。遅延ローディングと差分レンダリングに触れられていますが、もちろん他のフレームワークやライブラリでも実現されていることです。
徹底した標準化と教育で大規模開発にもAngularのパワー ー Toolsの杜 レポート (野村総合研究所 様) ー - GrapeCity.devlog
[https://devlog.grapecity.co.jp/entry/2018/08/06/forest-of-tools-nri]
(https://devlog.grapecity.co.jp/entry/2018/08/06/forest-of-tools-nri)
TypeScriptの利用やコンポーネント化によって高い生産性と品質を期待できるAngularに相性の良さを見出したことが、Angular採用の動機である、と述べられました。
もしかしたら比較対象が Vanilla JS や jQuery なのかもしれません。そういう意味ではこの記事を引き合いに出すのは間違いかもしれません。が、この記事がおもしろいのは、 Angular を宣伝しているように見えて実は Angular をこき下ろしているところです。
課題:
Angularを使ったアプリケーションでは「できること」が多く、一つの結果を得る際も開発者によってやり方は様々。この自由度の高さがプロジェクトに混乱をもたらす事態に。対策:
NRIではコーディングに関する標準化ルールを作成し、処理の方法やタイミングに一貫性を与えることで、このような状況を回避しているそうです。
セッションでは標準化の具体例として、100項目(!)以上に及ぶ作業一覧や、詳細に内容が定義された開発ガイド、コピー&ペーストで必ず同じ結果が得られるサンプルコード集などが紹介されました。
この対策で本当に良かったのでしょうか。その工数は他のことに使えなかったのでしょうか。 Lint のルール開発とか。 Angular 以外の選択肢とか。
フロントエンドフレームワーク(React, Vue.js, Angular)を選定するときのポイント | KEYPOINT – キー・ポイント株式会社 開発日誌
https://www.key-p.com/blog/staff/archives/107055
フルスタックでほぼすべてのモジュールが提供されていて、GoogleがメンテナスしているAngularの安定感は他のフレームワークとくらべ群を抜いているのは言うまでもないかと思います。
Google の権威に依拠しているだけですね。ほぼすべてのモジュールって何のことでしょうね。でもきっとこんな切り取り方をされる想定はしていなかったでしょう。なんかごめんなさい。
大規模Angular in 現場 / Large scale Angular in real world - Speaker Deck
https://speakerdeck.com/okunokentaro/large-scale-angular-in-real-world
Angular が大規模開発に向いているという主旨の主張は出てきませんでした。このスライドの主張に関しては基本的に私と同じ意見です。
- そもそも大規模開発をしない
そうそう。
- 画面間遷移は普通にページ更新
それそれ。
私の見解
コンポーネント定義の苦痛や RxJS の問題(覚えにくい、読みにくい、間違えやすい、コードが多様化する)、 6 ヶ月ごとのバージョンアップの影響は、規模が大きくなるほど深刻になるので、大規模開発でこそ Angular は危険でしょう。
まとめ
Angular だけでなく DI コンテナーと RxJS を巻き込んで批判したことで必要以上に敵を作りすぎた感はありますが、これが率直な気持ちです。
Angular はおすすめできません。
いまのところ無難に選ぶなら React 一択でしょう。少なくとも Vue.js 3 が枯れるまでは。(逆に無難さを求めないならそれこそメジャーな Angular を選ばなくても Svelte とか Cycle.js とか Vanilla JS とか Elm とか ReasonReact とか FlutterWeb とか AngularDart(!)とか攻めた選択肢がいくらでもありますよね。)
npm trends もご参考に。
@angular/core vs react vs vue | npm trends
https://www.npmtrends.com/@angular/core-vs-react-vs-vue
(ライブラリを選んで組み合わせるのが大変すぎて無理なら Next.js が第一候補になるかと思います。私にはまだ Next.js での開発経験がないので胸を張って Next.js をすすめるにはいたりませんが、 Next.js を気に入って使っている同僚はおります。)
現場からは以上です。
2020/02/03 追記
The State of JavaScript 2019 から Angular 利用者の悲痛な想いが見て取れるので紹介します。こんな記事を書いた私としては、「やっぱり! 私だけじゃないんだ!」と勇気づけられました。
https://2019.stateofjs.com/front-end-frameworks/#front_end_frameworks_section_overview