Angular初心者がハマりがちなポイントとその学習法
はじめに
こちらの記事は、Angular初心者の方が「この概念、Angularで初めて触れる…難しい」となることが多そうなポイントに対し、なるべくわかりやすい説明と、質の高い学習リソースへのリンクを紐付けたものです。
具体的には、以下のような人を想定しています。
- ディレクティブ、RxJS、リアクティブフォーム、DI、NgRx(状態管理)などがよくわからない
※随時更新予定です。
Angularは学習コストが高いフレームワーク?
Angularは「学習コストが高い」と言われることが多いです。
このあたりの議論は、lacolacoさんの記事を発端にして盛り上がっていたことがありますので、追ってみると面白いかと思います。
Angularの学習コストは本当に高いのか? - lacolaco
私自身も、初めてガッツリ(業務で)使ったJSフレームワークはAngularでしたが、キャッチアップしなければいけない概念は確かに多いとは思います。しかし、やり方次第でそれらを学習初期にもう少し効率よく学べたんじゃないかなと思う部分はあります。
JavaScriptを勉強したと思ったらTypeScriptも勉強しなきゃいけないじゃん!
これはもう頑張るしかない部分はありますが、以下のサイトで実務における必要最低限を勉強すると良さそうです。(JavaScriptの必要知識もあるので、JavaScriptに自信がない方でも大丈夫。)
コンポーネントはいいとして、ディレクティブとかパイプってなんなの?
ディレクティブ
おそらく初心者にとっては、コンポーネントは画面を構成する部品として直感的にわかると思いますが、ディレクティブはわかりにくいと思っています。手前味噌ですが、できる限りわかり易く説明した記事を書いてみたのでご覧ください。
【Angular】コンポーネントはわかるけど、ディレクティブがわからない人のための記事
ディレクティブは様々なDOM要素に対してロジックを共通化できます。
パイプ
パイプも、最初は取っつきにくいと思います。例えば、uppercase
パイプを使って変数test
に格納された文字列を大文字にするコードは以下のようになります。
{{ test | uppercase }}
このように、処理したい対象が一番最初に来て、|
のあとに実行したい変換(パイプ名)を書いていくため、関数呼び出しなどと順番が異なり直感的にわかりにくいという人が多いのではないでしょうか。ただ、ここは慣れです。
また、コンポーネントに例えば以下のようにメソッドを定義すれば、同じことはできます。それでもパイプを使うのはなぜでしょうか?
toUpperCase(str: string): string {
return str.toUpperCase();
}
上記のように1つのコンポーネントのみにロジックが書かれている場合、他のコンポーネントからは呼び出せません。パイプを使えば、このような値の変換をコンポーネント間で共通化できるというメリットがあるのです。[1]
ディレクティブやパイプの使い所
いずれも処理の共通化をする上で便利です。Angular標準やライブラリで提供されているもの以外にも、自分でオリジナルなものを作ることができるので、プロジェクトに応じて作ると良いです。
RxJSがわからない
そもそも、RxJS=Reactive Extensions for JavaScript であり、「リアクティブ・プログラミング」というプログラミングの考え方(「プログラミングパラダイム」、とか言ったりします)をJavaScriptで実現するためのライブラリです。なのでJavaScript以外にも、RxJavaとかRxSwiftとか、いろんな言語向けにライブラリが提供されています。
プログラミング初心者だと、書いたコードが上から順に実行されていく世界[2]以外は体験したことがない、という人が結構いるような気がしているのですが、「リアクティブ・プログラミング」の理解のためには、その世界は一度出る必要があります。
触りとして、このようなRxJSのイメージを掴むのにおすすめな記事を紹介しておきます。
まずはObservableを理解する
一番最初に慣れるべきはObservableでしょう。
Observable
は川に例えられることが多いです[3]。Observable
型の変数があったとすると、イメージすべきは「値が入った箱」(プログラミング学習初期によく使われる例え)ではなく、「値が流れてくる川」です。
そもそも、「Observable
をどうやって作るのか」という問題はありつつも、初心者の段階だと「すでに作られたObservable
から値をどうやって出すのか?」という壁にぶつかることが多い気がしていますので、そこを理解するための説明をします。(多少厳密性は犠牲にしています。)
例えば…以下のようなObservable型の変数があったとして(Angularでは慣例としてObservable
型の変数の名前は、末尾に$
を付けます[4]。)
test$: Observable<number>;
ここに流れてくるnumber
型の値を取り出して何らかの処理を実行するには、subscribe
します。ポイントは、test$
に対して新しい値が流れてきたタイミングで、中に書いたconsole.log
が実行される、ということです。(プログラムが上から順に実行される世界とは違う。)
test$
.subscribe(
(v: number) = {
console.log(v);
}
);
Observable
に流れてくる値に対して、色々処理をするために使われるのがオペレータです。例えば以下のように、流れてくる値のうち5の倍数だけにフィルタリング(filter
オペレータ)して、それらをさらに3倍する(map
オペレータ)みたいなことができます。(サラッと書いてますが、オペレータはpipe
を使って複数指定できます。)
test$
.pipe(
filter(n => n % 5 === 0),
map(n => n * 3)
)
.subscribe(
(v: number) = {
console.log(v);
}
);
RxJSのオペレータ多すぎない?
tap
とかmap
とかfilter
とか、いろんなオペレータがあるし、switchMap
とかmergeMap
、concatMap
のように似たようなものも多くて、最初は頭が混乱しちゃいます。
これについてはAngularの公式ドキュメントで、「よく使われるのはこのへん」というのを20個くらい示してくれています。
Angular 日本語ドキュメンテーション - RxJS ライブラリ
また、上記のオペレータのほとんどがわかりやすく解説されているQiitaを紹介しておきます。
RxJS入門#2図でわかるOperators - Qiita
それぞれのRxJSオペレータの挙動の勉強方法
よく使われるもの以外は、必要に応じて調べると良いと思います。RxJSの公式リファレンスがおすすめです。
こちらのリファレンスでも使われていますが、RxJSの説明にはよく「マーブル図」が使われるので、見方をマスターしておくと困らないです。
DI(依存性の注入)がわからない
DIはRxと同様、Angularに限らない一般的なソフトウェアパターンです。(「依存性の注入」という言葉だけ聞いて、最初から理解できる人は果たしているんだろうかと個人的には思ってしまいます。。)
Angular初心者が最初に出会うDIは、おそらくコンポーネントのconstructor
の引数として、Angularで提供されている何かや、自分で作ったサービスを指定するというものでしょう。
constructor(
private route: ActivatedRoute,
private hogeService: HogeService,
) { }
...
ngOnInit() {
this.route.paramMap.subscribe(params => {
// 何らかの処理
...
hogeMethod() {
this.hogeService.fuga();
...
なぜ、以下のようにコンポーネントの中で作成しないのでしょうか?
route = new ActivatedRoute();
hogeService = new HogeService();
そうしないことにメリットがあるからです。
もう一度constructor
でDIするコードを見てみましょう。
constructor(
private route: ActivatedRoute,
private hogeService: HogeService,
) { }
ここではコンポーネント側しか見えていませんが、実際に何が起きているかと言うと、
「このクラスからインスタンスを作成してください」という情報がコンポーネント外で登録されており(ちなみにこの登録場所をDIコンテナと言います)、使用する側がconstructor
に宣言(依存を要求)すると、インスタンスが作成され注入されます。このやり方を採用すると、例えば以下のようなメリットがあります。
- 同一インスタンスを使用する範囲を任意に指定できる。例えば、コンポーネントごとに異なるインスタンスを作成することもできるし、アプリケーション全体で同じインスタンスを使わせることもできる。
- テストが容易になる。テストのときだけ、モックのインスタンスを注入するなどが可能。
AngularのDIの仕組みや、細かい設定については以下の記事から入門していくと良いでしょう。今回わかりやすさのために、クラスのインスタンス化の場合を紹介しましたが、より一般的にはDIでは「オブジェクト」を注入できるので、定数値や環境変数など固定のオブジェクトを指定したり、オブジェクトを作成するファクトリ関数を指定することもできます。
Angular8 Dependency Injection まとめ - Qiita
NgRxって?状態管理って何?
状態管理はAngularに限らずフロントエンドフレームワークで必要となってくることなので、まずは以下の記事を読んでほしいです。
複雑な状態管理はAngular標準の機能だけでは難しいので、外部ライブラリを使います。最もよく使われているのはNgRxです。
NgRx
NgRxはReduxというReactの状態管理フレームワークから影響を受けており、そのReduxはFluxというアーキテクチャ(単方向データフローが特徴)から派生したものです。(このあたりのワードをなんとなく抑えておくと、フロントエンドの話についていきやすくなります。)
NgRxの入門記事をご紹介しておきます。
- Angularのngrxを使って状態管理を行う(理論編) - Qiita
- Angularのngrxを使って状態管理を行う(実装編:初期設定~エフェクト設定) - Qiita
- Angularのngrxを使って状態管理を行う(実装編:エンティティ設定) - Qiita
その他のライブラリ
NgRxに比べると知名度は低いですが、他にもライブラリがあります。
NgRxと同じくReduxベースですが、よりボイラープレートを少なくしたNGXSや、
同じくReduxベースで、シンプルさが特徴のAkitaなんていうのもあります。
-
他にもパフォーマンス上の都合などからパイプを使うべき場面を判断します(参考:https://qiita.com/kohashi/items/2a3664750a44fe6a4bee ) ↩︎
-
「手続き型プログラミング」と言われる ↩︎
-
川じゃなく、蛇口のほうが適切なんじゃないかという意見も見たことがあります。このへんはしっくりくる例えを見つけていただければ良いのかなと思います。 ↩︎
Discussion