👌

Angular初心者がハマりがちなポイントとその学習法

2020/11/05に公開

はじめに

こちらの記事は、Angular初心者の方が「この概念、Angularで初めて触れる…難しい」となることが多そうなポイントに対し、なるべくわかりやすい説明と、質の高い学習リソースへのリンクを紐付けたものです。

具体的には、以下のような人を想定しています。

  • ディレクティブ、RxJS、リアクティブフォーム、DI、NgRx(状態管理)などがよくわからない

※随時更新予定です。

Angularは学習コストが高いフレームワーク?

Angularは「学習コストが高い」と言われることが多いです。

このあたりの議論は、lacolacoさんの記事を発端にして盛り上がっていたことがありますので、追ってみると面白いかと思います。

Angularの学習コストは本当に高いのか? - lacolaco

私自身も、初めてガッツリ(業務で)使ったJSフレームワークはAngularでしたが、キャッチアップしなければいけない概念は確かに多いとは思います。しかし、やり方次第でそれらを学習初期にもう少し効率よく学べたんじゃないかなと思う部分はあります。

JavaScriptを勉強したと思ったらTypeScriptも勉強しなきゃいけないじゃん!

これはもう頑張るしかない部分はありますが、以下のサイトで実務における必要最低限を勉強すると良さそうです。(JavaScriptの必要知識もあるので、JavaScriptに自信がない方でも大丈夫。)

はじめに - サバイバルTypeScript

コンポーネントはいいとして、ディレクティブとかパイプってなんなの?

ディレクティブ

おそらく初心者にとっては、コンポーネントは画面を構成する部品として直感的にわかると思いますが、ディレクティブはわかりにくいと思っています。手前味噌ですが、できる限りわかり易く説明した記事を書いてみたのでご覧ください。

【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とかmergeMapconcatMapのように似たようなものも多くて、最初は頭が混乱しちゃいます。

これについてはAngularの公式ドキュメントで、「よく使われるのはこのへん」というのを20個くらい示してくれています。

Angular 日本語ドキュメンテーション - RxJS ライブラリ

また、上記のオペレータのほとんどがわかりやすく解説されているQiitaを紹介しておきます。

RxJS入門#2図でわかるOperators - Qiita

それぞれのRxJSオペレータの挙動の勉強方法

よく使われるもの以外は、必要に応じて調べると良いと思います。RxJSの公式リファレンスがおすすめです。

RxJS - API List

こちらのリファレンスでも使われていますが、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の入門記事をご紹介しておきます。

その他のライブラリ

NgRxに比べると知名度は低いですが、他にもライブラリがあります。

NgRxと同じくReduxベースですが、よりボイラープレートを少なくしたNGXSや、

同じくReduxベースで、シンプルさが特徴のAkitaなんていうのもあります。

脚注
  1. 他にもパフォーマンス上の都合などからパイプを使うべき場面を判断します(参考:https://qiita.com/kohashi/items/2a3664750a44fe6a4bee ) ↩︎

  2. 「手続き型プログラミング」と言われる ↩︎

  3. 川じゃなく、蛇口のほうが適切なんじゃないかという意見も見たことがあります。このへんはしっくりくる例えを見つけていただければ良いのかなと思います。 ↩︎

  4. https://angular.jp/guide/rx-library#observables-の命名規則 ↩︎

Discussion