lacolaco

唐揚げとアニメとプログラミングが大好きです

持続可能なAngularアプリケーション開発のために大事なこと

Webにかぎらず、アプリケーションというのは作って終わりではなく、その後も継続して改修・改善されていくケースが多い。受託で開発して納品して終わりというケースでも、納品した先にメンテナンスする人がいる。

この記事では、Angularアプリケーションの開発において、いかにメンテナンス性を維持して、持続可能なプロジェクトを構成するかについての個人的な見解をまとめる。

フレームワークを邪魔しない

Angularアプリケーションのメンテナンスにおいて、いちばん重要なことはいかにAngularのアップデートを阻害しないかという点に尽きる。 これはAngularに限った話ではなくフレームワークと呼ばれるものを使うなら常に必要なことであるし、 アップデートが定期的に降ってくることが決まっているAngularであればなおさらである。 アプリケーションの一番根幹となる部分の鮮度が落ちれば、その他の部分はそれに引きづられて腐ってしまう。

なので、Angularの新しいバージョンがリリースされたときにアップデートのブロッカーとなるものはなるべく作らないのが第一のポイント。 具体的には、ngx-**系のサードパーティ製のAngular用ライブラリは、極力減らす。 とはいえすべて使わない、というのはある程度の規模になると現実的ではないので、 利用するときは、そのライブラリのメンテナンスが頻繁に行われているかどうかをしっかりチェックするべき。

何か機能を実装するためにライブラリが必要だと思ったときには、まずフレームワークに依存しない実装のnpmモジュールを探す。 そのnpmモジュールとAngularとのインテグレーション部分は、自分でアダプターやブリッジを実装するのがベスト。

例: Google Analytics

例として、AngularアプリケーションでGoogle Analyticsと連携したいと思った時、angular google analyticsのように検索すると当然Angularのエコシステム上には専用のライブラリが出てくる。 例えば有名所で言えば https://github.com/angulartics/angulartics2 のような。

しかしこれはAngularのバージョンアップデートを阻害するリスクを冒してまで導入するべきものかどうか、個人的にはNoである。

Angularの基本的な機能を理解さえしていれば、ルーティングでURLパスが変わるたびにpageviewイベントをGAに送信するなど造作もない。たった5行のコードで書ける。

@Component({...})
export AppComponent {
  constructor(private router: Router) {}

  ngOnInit() {
    this.router.events.subscribe(e => {
      if (e instanceof NavigationEnd) {
        window.gtag('config', 'UA-*******-**', { page_path: e.urlAfterRedirects })
      }
    });
  }
}

クリックイベントの送信だって、(click) でイベントを受け取ったあとコンポーネント側で処理できる。 テンプレート上だけで簡単に済ませようと思うのは間違っていないが、Angularのバージョンアップデートを阻害するリスクと天秤にかけて考えるべき。 ディレクティブにしたって自分で書けばいい話だ。

Angularとサードパーティライブラリ

Angular用のサードパーティライブラリは、基本的には次の3種類になる。

  1. AngularのAPIブラックボックス化したユーティリティライブラリ
  2. 便利なディレクティブを提供するUIライブラリ
  3. 外部ライブラリをラップしたアダプタライブラリ

AngularのAPIブラックボックス化したユーティリティライブラリは極力避けよう。これは数行の手間を惜しむことで数ヶ月後の自分の首を締めることになる。特にこういう小さなユーティリティ系は作者が作って公開しておしまいになるケースが多く、そうして結局あとで自分で書くことになる。

UIライブラリも要注意。たくさんのコンポーネントのテンプレート内に散らばり、ライブラリを差し替えようとなったときにも一苦労になる。Angular MaterialやClarity、Ignite-UIなど、企業レベルでメンテナンスされていると安心して使える。

アダプタライブラリは、細かく分けると2種類ある。その違いはアダプタされた先のコア部分がAngularに依存するかどうか。 例えば、先ほどのGoogle AnalyticsをラップしたAngularticsやAngularFireなどは、コア部分は普通のJavaScriptなのでラップするのは自分でも簡単にできる。 よくあるのはFirebaseに新機能が入ったがAngularFireに反映されてなくて使えない、みたいなケースで、そういうリスクもあるので注意が必要である。 Firebaseのラッパーくらいは自分で書ける範囲。

一方でApollo-Angularは少し毛色が違い、Apollo-AngularはApollo Clientのラッパーというよりは、AngularのHttpClientをベースにApollo Clientを初期化するためのブリッジのようなもの。 この場合はAngular HttpClientとのブリッジを自分で書くのはApollo Clientの内部ドメインに精通している必要があるので少し難しい。 とはいえ Angularとaxiosを使ったHTTP通信 - lacolaco でも述べたように、Angular HttpClientを使わなければいけない理由などどこにもないので、標準のApolloClientを使ってしまうのもよいだろう。このあたりはそのライブラリのメンテナンス状況と信頼度に応じて考えるべきところ。

Angular用に作られたサードパーティライブラリを採用できる条件は次のようになる。

  1. その機能はAngularに依存するか => しないなら非依存のnpmパッケージを探す
  2. その機能はユーティリティ(いくつかのAngular機能のショートハンド)に過ぎないか => そうなら使わない
  3. そのライブラリはこまめにメンテナンスされているか => そうでないなら使わない

Angularに依存する部分と、そうでない部分を明確にする

アプリケーションの設計のなかで、Angularに依存すべき部分とそうでない部分を明確に分けておくことが大事。 ビュー層はどうしようもなくAngularの領域なので、コンポーネント、ディレクティブ、パイプの中にドメイン固有のロジックを持たない、ビューとしての仕事だけに専念させる。

ドメインロジックをまとめたサービスクラスへの@Injectable() は特に内部にAngularが侵入するわけではないので、大きな問題にはならない。 ただし、ドメインロジックの中でもAngular APIが必要になることがある。たとえばタイトルを変えるためのTitleサービスや、現在のURLを扱うためのLocationサービス、あるいはHttpClientやRouterなど。 そういった部分は、アプリケーションのインフラ層に逃し、やはりドメインロジックからは排除したい。下の画像の真ん中のロジック部分からはAngularを排除する。 ドメインロジックの変更をAngularに邪魔されないために、また、Angularのアップデートをドメインロジックで邪魔しないために。

f:id:lacolaco:20180515113943p:plain

ロジック内部の設計は自由だが、Angularに依存する部分とそうでない部分を明確にすると、自然と最低でもこの3層はできあがるはず。

プリミティブに書く

AngularもRxJSも、凝ろうと思えばいくらでもテクニック重視の書き方ができる。たとえばRxJSのpipeだけでほとんどの処理を済ませてしまうとか、たくさんのパイプを繋げてテンプレート内で処理を済ませてしまうとか。

書いてる間は気持ちがいいが、同僚や数ヶ月後の自分を困らせることになるのは誰の目にも明らか。なるべく素のTypeScriptでプリミティブに書けないかどうかを考えたい。 ライブラリのコードの最適化はライブラリが頑張るしかないが、TypeScriptで素直に書いておけばコンパイル時の最適化や実行時のエンジンでの最適化も受けやすい。async/awaitがそのいい例。 AngularやRxJSなどのアップデートでよく提供されるマイグレーションCLIも、使われている箇所が複雑になると適用漏れが生まれやすい。

テンプレート自体にも要注意で、*ngIf*ngForngSwitch などでビューの構造を操作しまくるテンプレートが大きくなると読みづらい。 こういった構造ディレクティブの内側は、ある程度スコープが切られた小さなテンプレートになるはずなので、その単位でコンポーネント分割するとけっこう見通しが良くなる。

こういったテンプレートから

<ng-container *ngFor="let user of users">
  <div class="user">
    <div>{{user.name}}</div>
  </div>
</ng-container>

このように切り出すのがよい。

<ng-container *ngFor="let user of users">
  <user-list-item [user]="user"></user-list-item>
</ng-container>

結果的に、構造を担うコンポーネントと、表示を担うコンポーネントが分かれていくので、コンポーネント設計としてもオススメの考え方である。

まとめ

持続可能なメンテナンス性の高いAngularアプリケーションを開発するために重要なことは以下の点。

  • Angularのバージョンアップデートを邪魔しない
    • Angularの特定バージョンに依存するサードパーティライブラリは極力減らす
    • Angularにない機能にはまずプレーンなnpmライブラリを探し、アダプターが必要なら自分で書くようにする
  • コードベース中で、Angularに依存する領域とそうでない領域を明確にする
  • 複雑なAPIを使いこなすよりも、プリミティブに書く

Angular v2からv6までの変化をまとめてみた

rdlaboさんがしっかりGW明けにIonicの記事書いてくれたので、僕もAngularのv2からv6まで、3年弱の変遷についてまとめます。

Ionic 2 から 4 への、この2年間の進化を振り返る

前Angular v2時代

現行Angularの歴史を話し始めるとAngularDart 0.x-1.xからの話になります。 実はAngularDart v1は、停滞したAngularJSの改革をおこなうための、仮説検証の場でした。 僕がAngularDart 0.xから1.xを使って開発していたのが2014年末〜2015年あたりなので、だいたい3年半くらい前の話です。

ここで生まれた基本的な概念は今でもしっかりとAngularに残っています。例えば次のようなもの。

これらはDartの言語機能やWeb Components仕様の整備により実装できた新しいAngularの形で、これをAngularJSの次世代バージョンとして実現するために必要な言語機能の仕様が AtScript として誕生し、これをもとにAngular 2の開発が進められました。

結局その後は紆余曲折ありAtScriptは必要なくTypeScript+デコレータで実現することになりました。

Angular v2

2015年12月に、Angular v2はbeta.0をリリースし、2016年9月14日に2.0.0がリリースされました。

ちゃんと掘り下げると無限に長くなるので、この時期に導入された重要な概念をおさらいするだけに留めます。

オフラインコンパイル

現在ではAngularのAoTコンパイルは当たり前になっていますが、当時はじめて"オフラインコンパイル"というものが発表されたときは困惑しました。 あらためて当時のプロポーザルを見ると、まさにこれって今実装されようとしているngIvyそのものじゃないかと思いました。 これまでのAoTではまだ不十分でしたが、ついに3年越しの成就となろうとしてます。感慨深い。っていうかTobias氏が頭良すぎる。

lacolaco.hatenablog.com

Angular 2 offline template compilation - Google ドキュメント

https://ng2-info.github.io/2016/01/angular2-in-2016/#even-faster-angular-2-tobias

実際にAoTコンパイルが使えるようになったのはrc.2からでした。2016年5月のことなので、ちょうど2年前。ngcちゃんも2歳になりました。

AngularJSへの .component 逆輸入

AngularJSからAngular 2への移行をスムーズにするため、マインドセットを統一するためのAPI逆輸入がおこなわれました。 コンポーネントベースの設計になることで、コンポーネントルーターを起点としたマイグレーションが可能になる、という構想でした。

当時は angular-component-router 自体は失敗しましたが、考え方自体は今のngUpgrade APIと同じなので、やっぱりAngularコアチームは頭良いですね。

Animation API

この頃、Web Animations APIを元にしたAnimation APIを作ることが決まりました。今でもしっかり続いています。

Language Service

テンプレート内でコンポーネントの型情報を元に型チェックや入力補完をおこなう機能の提案がなされたのが実はbeta.5のころです。 実際に実装されて使えるようになったのは2.3リリースの頃ですが、いまでは開発中に無くてはならないものになっています。

https://ng2-info.github.io/2016/02/beta-5-has-released/#template-services-plan

Angular CLIとスタイルガイド

John Papa氏とMinko Gechev氏のふたりが草案を作ったAngular Style Guideは、いまでも公式ドキュメントとしてメンテナンスされつづけています。 そしてAngular CLIではそのスタイルガイドに沿ったコード生成をおこなっています。

SystemJSからwebpackへ

アルファ版のころから続いていたSystemJSベースのセットアップが、webpackベースに変わりました。 v6になった今でもAngular CLIはwebpackを利用していて、Tree Shakingなどいろいろな最適化ができているのでナイスな決断だったといえます。

Forms APIの改革

RCの中でもっとも大きく変わったAPIのひとつがFormsですね。テンプレート駆動フォームと、リアクティブフォームが明確に分割され、選択できるようになりました。 この頃はテンプレート駆動がメインで、リアクティブがオプショナルという立ち位置でしたが、いまやリアクティブがメインで、テンプレート駆動がオプショナルとなりつつあります。 パラダイムの変遷を感じます。

Routerの改革

RCの中でもっとも大きく変わったAPIその2、Routerです。当時はてんやわんやでした。

AngularJS: Improvements Coming for Routing in Angular

Victor Savkin氏により実装されたRouter v3はその形をほとんど変えないまま今のv6まで続いています。 Victor Savkin氏はAngularコアチームの中でもとびきり頭が良い一人ですね。

NgModuleの導入

Angular 2のRCといえばNgModuleというくらい悪名高い破壊的変更です。いまでは当たり前ですが、これも当時は衝撃でした。

https://ng2-info.github.io/2016/07/preparing-for-ngmodule/

v6でも現役な、Angularの根幹のAPIですが、そのあり方は変わろうとしています。詳しくはv6のセクションで述べます。

Custom Elements対応

rc.6で追加されたのが CUSTOM_ELEMENT_SCHEMA です。Angularのテンプレート内でAngularが知らないカスタム要素を使うための機能です。 これによりWeb Components仕様に則って作られたCustom Elementsとの協調が可能となりました。 いまでも現役な重要機能です。

v4

半年後の2017年3月23日にv4.0.0がリリースされました。

AngularJS: Angular, version 2: proprioception-reinforcement

AngularJS: Angular 4.0.0 Now Available

v2.xからv4の中で変わっていったものたちを振り返っていきましょう。

Reactive Angularに向けた変化

この頃からリアクティブプログラミングをサポートするための変更が進み始めます。 *ngIf*ngForasync as 記法で非同期データと値を保持できるようになったのがv4のことです。今ではこれ無しのテンプレートは考えられないです。

<div *ngIf="userList | async as users; else loading">
  <user-profile *ngFor="let user of users; count as count; index as i" [user]="user">
User {{i}} of {{count}}
  </user-profile>
</div>
<ng-template #loading>Loading...</ng-template>

Angular Universalの刷新

ベータ版のころからサードパーティとして続いていたAngular Universalのコア部分をAngularコアチームが回収し、@angular/platform-serverとして再実装しました。 複雑になっていたAPIを整理し、renderModuleだけを提供するシンプルなパッケージに生まれ変わりました。 platform-serverはこの頃と今でもあま変わっていませんが、platform-serverを元にした新しいAngular Universalは独自に進化を続けています。

https://github.com/angular/universal

f:id:lacolaco:20180508103229p:plain

angular.ioのAngular化

v4.2のリリースにあわせて、ようやく公式ドキュメンテーションサイトのangular.ioがAngularで再実装されました。 いまでもコアチームによるAngularのPWA実装の例として活発にメンテナンスされています。

AngularJS: Angular 4.2 Now Available

Animation APIの改革

stagger()group() など複雑なアニメーションを実現するための新しいAPIがv4.2で導入されました。

A New Wave of Animation Features in Angular - yearofmoo.com

HttpClientの導入

@angular/common/http パッケージが公開され、新しいHTTPクライアントが使えるようになったのがv4.3です。

Angular 4.3 Now Available – Angular Blog

v5

v4から半年後、2017年11月2日に2回目のメジャーアップデートであるv5.0.0がリリースされました。

Version 5.0.0 of Angular Now Available – Angular Blog

Build Optimizer

v5の頃から、Angularは機能の追加よりもバンドルサイズの改善に注力する傾向が強まります。 Build Optimizerはwebpackのプラグインとして提供され、Angularのアプリケーションのバンドル時に様々な最適化をおこない、バンドルサイズを削減します。

AngularコンパイラのTypeScript Transformer化

AngularのテンプレートコンパイラがTypeScriptの標準拡張機能に準拠することにより、デコレータのコンパイル処理に介入できるようになりました。 これはテンプレートの最適化をおこなうための足がかりでもあり、テンプレート中の無駄な空白を除去してバンドルサイズを削減することなどが可能になりました。

また、AoTコンパイルの軽量化により、ng serve --aot が可能になったのもこの時です。

さらに、デコレータ内でアロー関数を使ってもAoTコンパイルできるようになったのもv5です。v2から長くやっている方はもしかすると無意識に避けてしまっているのではないでしょうか。

Component({
  provider: [{provide: SOME_TOKEN, useFactory: () => null}]
})
export class MyClass {}

ロケール依存パイプの刷新

v5で最も衝撃の強かった変更はdatenumberなど、ロケールに依存する組み込みパイプの全面刷新です。 それまではWeb標準仕様のI18n APIに準拠して実装されていましたが、バグが多くブラウザ間の実装の差異に悩まされていたため、それをやめてAngular内部にロケールデータを持つことになりました。

https://github.com/angular/angular/blob/master/CHANGELOG.md#i18n-pipes

Zone.js非依存化への歩み

v5では、Angularアプリケーションの実行時にZone.jsを使わない選択肢が選べるようになりました。

platformBrowserDynamic()
  .bootstrapModule(AppModule, {ngZone: 'noop'});

バンドルサイズの軽量化や、エキスパートな開発者による独自のパフォーマンスチューニングを可能にするための機能です。

Angular MaterialとCDK

ついにようやくAngular MaterialがStableになり、そのコアであるComponent Dev Kit(CDK)も公開されました。

Angular 5.1 & More Now Available – Angular Blog

Angular CLIのPWAサポート

Angular CLIがService Workerの生成や、Angular Universalを利用したApp Shellのレンダリングなどをサポートし、Angular for PWAの風が吹いてきました。 v6では ng add @angular/pwa だけでPWA化が済んでしまうほどになりました。

Angular Progressive Web App Guide

v6

そして2018年5月4日、6.0.0がリリースされました。

Version 6 of Angular Now Available – Angular Blog

Angular Elements

v6といえばAngular Elementsです。 まだまだ実験的なAPIではありますが、AngularのコンポーネントをCustom Elements化できる機能が公開されています。

https://angular.io/guide/elements

アプリケーションのためのフレームワークだったAngularから、WWWの世界へ進出していくための大きな一歩です。

Angular CLIの大改革

Angular CLIがv1.7からv6へアップデートし、かなり大きな進化を遂げました。

アプリケーションだけでなくライブラリ開発も可能になり、ng updateコマンドやng addコマンドなど新しい機能もたくさん追加されました。

新しいDI機構

NgModuleの導入以来ずっと変化のなかったDI機能に、新しい仕組みが導入されました。

Tree Shakable Providers と呼ばれる機能により、DIでプロバイドされる@InjectableなクラスをNgModuleのprovidersに渡さなくてもよくなります。 代わりに、@Injectable({providedIn}) という新しいAPIで、自身がプロバイドされるモジュールのスコープを持つようになります。

import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root',
})
export class MyService {
  constructor() { }
}

Angular v6で導入されるTree-Shakable DIの紹介 - lacolaco

https://angular.io/guide/dependency-injection#tree-shakable-providers

静的な参照がNgModuleから消えるため、Tree Shakingによって実際にコンストラクタの型パラメータとして参照されているクラスだけがバンドルに含まれるようになります。

RxJS v6

Angular v6が依存するRxJSのバージョンもv6にアップデートされました。 RxJSのメンテナであるBen Lesh氏もAngularコアチームに入ったことによりスムーズに連携できるようになった印象です。

RxJS v6はTree Shakingサポートによってバンドルサイズを大きく減らすことができるため、Angularにとってもかなり嬉しいアップデートです。


まとめ

結構長くなりました。 v2以前からv6まで変化の流れを追ってみましたが、今あるAngularの機能の大半はv2のころのままですね。 新し目の機能についても構想自体はv2以前からのものもあり、2.0.0以降の変化は主に開発者支援や、バンドルサイズ削減に貢献するものがほとんどだなというのがわかります。

  • v2: フレームワークとしてのAngularの完成
  • v4: フレームワークのブラッシュアップ
  • v5: プラットフォームとしてのAngular: Material, CDK, PWAサポート
  • v6: プラットフォームの拡大: Angular Elements

といった感じでしょうか。

今後もAngularはフレームワークとしてだけでなくプラットフォームやエコシステムとして広がっていくこと間違い無しなので、マクロな視点で見ていくのが大事ですね。

ご報告

私事で大変恐縮ではございますが、

結婚しましたことを

ここにご報告いたします。

 

 

 

 

結婚したからといって

ソフトウェアエンジニア・lacoの活動やスタンスには特別な変化はございません。

 

 歳を重ねても、環境が変わっても、結婚しても、 私は私!

 

 

 

ですので、これからも

ソフトウェアエンジニア・lacoが

今まで関わってきたコミュニティやOSS

今後関わるコミュニティやOSS

変わらず一緒に応援して頂けたなら

とってもとっても幸せです🍣

 

 

 

 

 

 

こんな私ですが

これからも、どうぞよろしくお願いします!

 

 

 

 


愛と感謝を込めて。

 

 

 

 

 

 

2018.05.04     laco

 

参考文献: 結婚のご報告。 | 佐藤聡美オフィシャルブログ「おさとう缶。」Powered by Ameba

 


f:id:lacolaco:20180504194155j:image

P.S. ケーキ、Marrigeになっててaが抜けていました。

Custom ElementsとEventTargetの話

Shadow DOMのHayato Itoさんと、Custom ElementsとEventTargetについてちょっと議論できた話。(ありがとうございました!)


先日、Web Components Cafeで登壇しました。

slides.com

最近自分の中でCustom Elementsの盛り上がりが強くて、 単純なPresentationalなコンポーネントだけじゃなく、 ある程度の機能を備えたマイクロアプリケーションとしてのCustom Elementsをどう設計・運用するか、みたいなところを考えてる。

イベントの話

Custom Elementsで分断されたマイクロアプリケーション間でコミュニケーションしようとすると、当然それらの外側にある何かを介するしかなくて、 現実的には何かしらのイベントバスが必要になる。パッと思いつくのは window をイベントバスとして使うケース。

f:id:lacolaco:20180425101709p:plain

これには問題があって、イベントは文字列で識別されるので、未知のタグやスクリプトから同じ名前のイベントが通知されるおそれがある。

f:id:lacolaco:20180425101949p:plain

これを解決するためには、そのドメインに閉じたscopedなイベントバスが欲しいという話。

CustomElementRegistry

で、そのスコープって今仕様が議論されているCustomElementRegistryと同じ粒度なんじゃないかと思い、 GitHubEventTargetRegistryみたいなものがあると良いのでは!?というコメントを書いてみた。

I think scoped root EventTarget also will be needed. Separated elements can only communicate each other via its outer event bus, window. Events are identified by its name as well as elements. So, as the same idea, I guess something like EventTargetRegistry will be important.

https://github.com/w3c/webcomponents/issues/716#issuecomment-383540589

今思うとかなりふわふわしてるコメントだけど、ありがたいことにShadow DOMのHayato Itoさんが返信してくれた

@lacolaco Could you kindly give us an example how scoped root EventTarget works? Pseudo-code snippet might be helpful to understand the basic idea.

I think I can understand what problem you are trying to solve, but it is unclear to me how EventTargetRegistry works.

https://github.com/w3c/webcomponents/issues/716#issuecomment-383780403

改めてユースケースを考えてみると、新しいRegistryが必要なことはなくて、CustomElementRegistryそのものがEventTargetになってくれたらよさそうだった。

@hayatoito Just an idea, for example, I think CustomEelementRegistry can be an EventTarget.

const xRegistry = new CustomElementRegistry();

class XFoo extends HTMLElement {
  constructor() {
    super();
    this.addEventListener('click', () => {
      // dispatch a scoped event
      xRegistry.dispatchEvent(new CustomEvent('xEvent'))
    });
  }
}
class XBar extends HTMLElement {
  constructor() {
    super();
    // subscribe scoped events
    xRegistry.addEventListener('xEvent', () => {
      // ...
    });
  }
}

xRegistry.define('x-foo', XFoo);
xRegistry.define('x-bar', XBar);

f:id:lacolaco:20180425104452p:plain

これにはHayato Itoさんも同意してくれたんだけど、実は EventTarget って普通にnewできることを教えてくれた。

Thanks. Just in case, EventTarget is now constructible. Users can create their own EventTarget and use it for any purpose.

これ知らなかったのですべてひっくり返って「これでいいじゃん(いいじゃん)」になった。ありがとうございます。

new EventTarget()

windowやdocument、Elementなど addEventListenerできるオブジェクトはみんなEventTargetインターフェースを実装しているんだけど、 実は去年のwhatwg DOM Standardのアップデートで、開発者が自由にnew EventTarget() できるようになってた。知らなかった。

ついでにいえば、サブクラスを作ることもできるようになってた。知らなかった。

DOM Standard

github.com

MDNにも項目があった。知らなかった。

EventTarget() - Web APIs | MDN

ただ、ブラウザの実装状況はまだそれほどよくない。まだ使いづらい。

f:id:lacolaco:20180425103928p:plain

まとめ

  • CustomElementRegistryがEventTargetになったら直感的な気がする
  • EventTargetはnewできる
  • 現状は自前でEventBus作る感じになりそう。

なぜAngularJSに$httpが必要だったのか(あるいはAngular HttpClientの価値について)

lacolaco.hatenablog.com

「レールに乗っておいたほうがいいんじゃないの?」という声もあるとおもうので、 そもそもなぜAngularはHTTPクライアント機能をスタックとして提供しているのか、というところについて。

AngularJSには$httpが必要だった

遡ってAngularJSの話をすると、AngularJSにも組み込みのHTTPクライアント機能があり、そのAPI名からよく$httpと呼ばれています。

https://docs.angularjs.org/api/ng/service/$http

現在ではこんな感じのAPIになっています。

// Simple GET request example:
$http({
  method: 'GET',
  url: '/someUrl'
}).then(function successCallback(response) {
    // this callback will be called asynchronously
    // when the response is available
  }, function errorCallback(response) {
    // called asynchronously if an error occurs
    // or server returns response with an error status.
  });

この機能がAngularJSに必要だった理由は以下のダイジェストループによるものが大きいです。

AngularJSのダイジェストループ

AngularJSは内部のダイジェストループが繰り返し実行されることでコントローラーの変更がビューに反映されます。 一般にHTTPのレスポンスが返ってきたあとにはコントローラーの状態が変わり、取得した値を表示することになるので、 HTTPクライアントがAngularJSのダイジェストループをトリガーできる必要がありました。

必要がありました、とはいえ、AngularJSに詳しければ $scope.$applyAsync使えばいいじゃん、というのはありますし、それは正しいです。 しかしXHRが発火するさまざまなイベントのなかで適切にダイジェストループを回すのは難しく、 ダイジェストループのオーバーヘッドは大きいのでうまく出来ないとパフォーマンスが悪化する危険性もあります。

なので、簡単に使えるようにするために、AngularJSのコアと密接なHTTPクライアントが必要でした。

jQuery$.ajax

当時主流だった $.ajax と似たAPIを持つことで、導入の障壁を下げる狙いもあったかもしれません。

AngularのHttpClientはなんのためにあるのか

そうしたAngularJSの背景をもとに、今のAngularのHttpClientにはどういった価値があるのかを改めて考えることができます。

Change DetectionとZone.js

Angularにはダイジェストループはなく、ランタイムで発生するほぼすべての非同期処理はZone.jsによって捕捉され、Angularの変更検知システムへディスパッチされます。 XHRを使おうと、Fetch APIを使おうと、非同期処理の終了後にはデータバインディングに基づいてビューが更新されます。

つまり、ビュー描画システムとしてのAngularと、AngularのHTTPクライアントは完全に分離可能だということです。

RxJS親和性

ではなぜAngularのHTTPクライアントが存在するのか、それはPromiseではなくObservableベースのHTTPクライアントを導入するためです。 Angularはコアの非同期処理から周辺モジュールまで、多くの非同期処理をRxJSのObservableベースで実装しています。 そのため、HTTPクライアントのAPIもObservableを返すように実装されていたほうが、アプリケーション全体でRxJSの恩恵を受けることができます。

RxJSに精通し、使いこなせる開発者にとっては、非常に強力な武器になるAPIなのは間違いないです。 そしてXHRの各種イベントをわかりやすいAPIとしてRxJSで実装するのは骨が折れるので、この部分がAngular公式に提供されていることが最大の価値です。

フルスタックの信頼性

フルスタックであることでモジュール選択のコストをさげることが目的の場合には迷わず選択できる公式パッケージには大きな意味があるでしょう。

いつAngular HttpClientを使わないのか

想像するに次のような場面でサードパーティや独自実装のHTTPクライアントを採用するモチベーションがあると思います。

  • RxJSの学習コストを下げたい
    • JavaScript自体の練度が低い場合、Promiseもわからない状態でObservableに手を出すのはハードルが高いでしょう
    • あるいはRxJSをアプリケーションコードでは書きたくないという好みの問題もありでしょう
  • すでにサードパーティHTTPクライアントの知見を持っており、流用できる
    • お気に入りのものがあればそれを使ってよいと思います。

まとめ

  • AngularJSの$httpとAngularのHttpClientは立場が違う
    • $http はどうしても必要だった
  • RxJSとどう付き合うかで考えるとよい
  • お気に入りのライブラリがあるならそれを使って楽しく開発するのがよい