lacolaco

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

なぜ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とどう付き合うかで考えるとよい
  • お気に入りのライブラリがあるならそれを使って楽しく開発するのがよい

Angularとaxiosを使ったHTTP通信

題して「頼りすぎないAngular」ということで、Angularの層をなるべく薄くアプリケーションを作るにはどうすればいいかというのを考えるシリーズです。 Angular良さそうなんだけどロックインされて捨てにくそう、という人々向けに、コードのモジュール性とフレームワーク非依存性を重視した実装パターンを試行錯誤します。

第一回目はAngularのHttpClientを覚えずに、人気のnpmモジュールである axios を使ってAngularアプリでAjaxする例を紹介します。 axiosはTypeScriptの型定義を同梱していて、インターセプターなどAngularのHttpClientと同じような機能が揃っています。

Live Example

今回の完成形はこちらです。

stackblitz.com

Random User Generatorからユーザー情報のJSONを取得し、画面に表示するアプリケーションです。

HttpClient

さて今回はAngular公式のHttpClientモジュールを使わずフレームワーク非依存のaxiosを使ってHttpClientを作ります。 次のようなファイルでアプリケーション用のカスタムインスタンスを生成してexportします。 今回は何もしませんが実際はデフォルトのヘッダを追加したりインターセプターを追加したりいろいろすると思います。

import axios from 'axios';

const instance = axios.create();

export default instance;

UserRepository

次に、作成したHttpClientを使ってAPI呼び出しを行うためのサービスクラスを作ります。 単純にimportして使うだけです

import { Injectable } from '@angular/core';
import httpClient from '../infrastructure/http-client';

@Injectable()
export class UserRepository {

  async random() {
    const { data } = await httpClient.get('https://randomuser.me/api/');
    const { results: [user] } = data;
    return user;
  }
}

作成したサービスクラスをAppModuleに登録します。

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

import { AppComponent } from './app.component';
import { UserRepository } from './repository/user';

@NgModule({
  imports: [BrowserModule],
  declarations: [AppComponent],
  bootstrap: [AppComponent],
  providers: [UserRepository]
})
export class AppModule { }

AppComponent

最後にコンポーネントからサービスを利用します。ここはAngularのDIを使います。

import { Component } from '@angular/core';
import { UserRepository } from './repository/user';

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  user: User | null = null;

  constructor(private userRepo: UserRepository) { }

  ngOnInit() {
    this.fetchUser();
  }

  async changeUser() {
    this.user = null;
    await this.fetchUser();
  }

  private async fetchUser() {
    this.user = await this.userRepo.random();
  }
}

テンプレートでは名前と写真を表示して、ボタンを押すとchangeUser()メソッドをトリガーするようにしています。これで完成です。

<h2>Angular with axios</h2>

<ng-container *ngIf="user as user">
    <h2>{{ user.name.first + ' ' + user.name.last | titlecase }}</h2>
    <img src="{{user.picture.large}}">
</ng-container>
<button (click)="changeUser()">Change User</button>

テストとDI

サービスクラスのUserRepositoryでは直接httpClientをimportして参照しましたが、AppComponentではDI経由でUserRepositoryを参照しました。 この違いは、ユニットテストをどう書くかという観点で分かれています。

axiosはmoxiosというパッケージを使うことで簡単にaxiosのインスタンスをモック化できます。 そのため、httpClientの初期化のテストにおいても、UserRepositryの振る舞いのテストにおいても、DIは必要ありません。

しかしAppComponentからUserRepositoryを直接参照すると、簡単にはモックできません。 なのでテスト時にUserRepositoryのモックを提供できるようにDI経由で参照しています。

利点・欠点

利点

  • axiosは有名で人気なライブラリなので学習しやすい
  • HttpClient部分はAngularじゃなくても使える

欠点

  • RxJSの恩恵を受けられない
    • 遅延実行
    • 複数値の返却

RxJSの恩恵については、UserRepositoryの層でfromPromise関数などを使ってObservableを返すようにすれば少しだけ解決します。 しかしFlux的な設計をするとなるとObservableなのはストアだけで良くて、fetch自体は単発で終わるほうが扱いやすいので特に欠点ではない気もしています。

まとめ

  • AngularのHttpClientは必須ではない
  • axiosのようなライブラリを使ってフレームワーク非依存の独自HttpClientを作れる
  • DIするかどうかはテストしやすさを考える

次回は未定です。

『組織は意思決定を生産する工場である』ファスト&スロー(下)読後メモ

lacolaco.hatenablog.com

以前に紹介した「ファスト&スロー」の上巻に続き、勢いで下巻まで読み終わったので、下巻の方の感想を書く。 タイトルは下巻の中で最も強く心に残ったフレーズ。

上巻に引き続き人間や組織の意思決定について学びの多い本だったが、上巻よりも「リスク」と「予測」にフォーカスした実践的な内容になっていて、活用したくなることこの上なしだった。

今回もkindleでマーカーを引いていた部分を引用しながら感想を書き連ねていく。

印象深かった部分

「直感」について

  • 人々が自分の判断に自信を持つときには、認知容易性と一貫性が重要な役割を果たしている。つまり、矛盾や不一致がなく頭にすらすら入ってくるストーリーは受け入れやすい。だが認知が容易でつじつまが合っているからといって、真実だという保証にはならない
  • 人々が自分の直感に対して抱く自信は、その妥当性の有効な指標とはなり得ない、という原則である。言い換えれば、自分の判断は信頼に値すると熱心に説く輩は、自分も含めて絶対に信用するな、ということ

  • 彼らの予測がことごとく外れるのは、予測しようとする事象が基本的に予測不能であることを反映しているにすぎない

  • 専門家自身が不可能なタスクをこなせると信じ込んでいたら、それを非難するのは正当だろう。予測不能な状況で直感が正しいと主張するのは、よくて自己欺瞞であり、悪くすれば悲惨な結果を招きかねない。有効な手がかりが存在しない場合には、直感の「的中」は幸運によるのであり、でなければ嘘である

  • 一定の規則性が存在しない状況では、直感は信用できない

  • プロフェッショナルの直感的なスキルの習得は、基本的には、質の高いフィードバックをすぐに得られるかどうか、そして練習し実践する機会が十分にあるかどうかにかかって

  • 専門的スキルの限界を認識していないことが、エキスパートがしばしば自信過剰になる一因だ
  • その直感が十分に規則性の備わった環境に関するものであって、判断をする人自身にその規則性を学習する機会があったのなら、連想マシンがすばやく状況を認識して正確な予想と意思決定を用意してくれるだろう。この条件が満たされているなら、あなたはその人の直感を信用してよい

エンジニアリングにおいても、経験を積むことで「良いコード」や「悪い設計」の判断は直感的にできてくると思う(だからこそプログラミングの教育は難しいのだけど)。 それにエンジニアリングをおこなう上で「見積もり」というタスクからは逃れられない。 どうにかして未来を予測する必要があるけども、結局予測は予測なので答えはなく、最終的には自分の直感に頼るしかない。なので、スキルとしての直感を磨かないといけない。 質の高いフィードバックをすぐに得られる環境に自分を置き、「直感」の精度を高めることを意識することで強く成長できる気がする。

「リスク」について

  • 経済学の標準的なモデルでは、人間は確率的に有利なときにのみリスクをとることになっている。言い換えれば、代償の大きい失敗の可能性をある程度まで受け入れるのは、成功の可能性がそれに見合うほど高いからである。だが私たちは、別の見方を提案した。
  • リスクの大きなプロジェクトを前にした意思決定者が、必ずとは言えないまでもしばしばゴーサインを出すのは、成功の確率を過度に楽観視しているからだ
  • リスクをとる人は、たいていは自分が失敗する確率を過小評価しており、猪突猛進した末に、本当の確率を思い知ることになる。リスクを読みちがえているせいで、実際には慎重とは無縁であるにもかかわらず、ちゃんと注意を払ったと思い込む。将来の成功に対して自信たっぷりのため、バラ色のオーラを発散し、首尾よく事業資金を獲得し、部下の士気は大いに高まる。... 行動を起こすべきときには、楽観主義はたぶん好ましい。
  • アメリカでは、スタートアップが五年生き延びる確率は約三五%である。だが自ら起業した人は、この統計が自分に当てはまるとは思っていない。ある調査によると、アメリカ人起業家は事業の継続性を期待する傾向が強く、「自社と同じような企業」が成功する確率を六〇%と見込む。これは実際の数字の倍に近い。そしてこれが自社のことになると、バイアスは一段と顕著になる。起業家の八一%は、自社の成功率を七〇%以上と見積もり、かつ三三%が失敗の確率はゼロだと豪語した
  • とはいえ起業家の楽観的なリスクテークが、資本主義経済を活性化させていることはまちがいない──たとえリスクをとる人の大半が最後は破綻するにしても。

どのようなときに人はリスクを受け入れがちなのか、という話。 楽観的な意思決定者は勇敢に見えるし、自信なさげなプロフェッショナルに仕事を依頼するクライアントは少ない。 「専門家」に求められるのは自分が知らないことを委ねられる存在であって、どれだけ誠実であっても信頼できなければ市場での競争では不利になる。

  • 自分の無知を率直に認める専門家は、おそらく自信たっぷりな専門家にとってかわられるだろう。なぜなら後者のほうが、顧客の信頼を勝ちとれるからである。不確実性を先入観なく適切に評価することは合理的な判断の第一歩であるが、それは市民や組織が望むものではない。危険な状況で不確実性がきわめて高いとき、人はどうしてよいかわからなくなる。そんなときに、当てずっぽうしか言えないなどと認めるのは、懸かっているものが大きいときほど許されない。何もかも知っているふりをして行動することが、往々にして好まれる。

このジレンマをどう乗り越えるかが、技術者倫理の観点から重要になると思う。 専門家だからこそわからないと言えることは往々にしてあるわけだけど、それは大衆が求めている答えではなく、失望を受けることもあるかもしれない。 とはいえそこで誠実さを捨ててしまったらその時点でプロフェッショナルではなくなっていると思う。

「組織」について

  • 何か重要な決定に立ち至ったとき、まだそれを正式に公表しないうちに、その決定をよく知っている人たちに集まってもらう。そして、「いまが一年後だと想像してください。私たちは、さきほど決めた計画を実行しました。すると大失敗に終わりました。どんなふうに失敗したのか、五~一〇分でその経過を簡単にまとめてください」と頼む。クラインはこの方法を「死亡前死因分析(premortem)」と名付けている。
  • チームがある決定に収束するにつれ、その方向性に対する疑念は次第に表明しにくくなり、しまいにはチームやリーダーに対する忠誠心の欠如とみなされるようになる。とりわけリーダーが、無思慮に自分の意向を明らかにした場合がそうだ。こうして懐疑的な見方が排除されると、集団内に自信過剰が生まれ、その決定の支持者だけが声高に意見を言うようになる。死亡前死因分析のよいところは、懐疑的な見方に正統性を与えることだ。さらに、その決定の支持者にも、それまで見落としていた要因がありうると考えさせる効果がある。
  • プロジェクトの結果を予想するとき、計画の成功は特定された事象なので、イメージしやすい。これに対して失敗の形はそれこそ無限にあって、注意の対象が分散しがちである。

死亡前死因分析はこの本で紹介されるエピソードのなかでもすごく実践的なアドバイスだったし、試してみたくなった。 成功へのステップ、山の登り方を計画し想像するのはポジティブだし楽しくイメージできるが、逆のイメージをポジティブに想像するのはそういう場を用意しないと難しい。

  • 目標に届かない失敗を避けようとする動機のほうが、目標を超えたいという願望よりもはるかに強く働く。 ... 目標というものは、がんばって達成しなければならないが、必ずしも上回る必要はない。だから首尾よく達成すると私たちは気を抜いてしまう。

計画というのは進捗のスピードに波があることを想定しておこなうものではあるが、多くの場合遅れだけを加味してバッファを積むことになる。 しかし波というのはプラス側に振れることも当然あるわけで、目標を達成したからといって気を抜いてしまわず、その分プラスに積むことで遅れたときの保険となる。

  • 狭いフレーミングを設定しがちな意思決定者は、リスクを伴う選択に直面すると、そのつど選択を決めることになる。リスクポリシーをあらかじめ決めておいて、問題が持ち上がったら必ず適用するようにすれば、はるかによい選択ができるだろう
  • 長期的な結果を伴う決定を下す際には、徹底的に考え抜くか、でなければごくいい加減にざっくりと決めるか、どちらかにしている。中途半端に考えるのがいちばんよくない。ことが起きてから、「あのときもう少し考えればもっといい決断を下せたのに」ということになる

意思決定はとてもコストの高い作業であって、そこにリスクが絡むとさらに難しくなる。 上巻から常に述べられてきたように、人間の思考によって正しく意思決定することは困難であるから、考える余地を仕組みやルールによってなくしてしまったほうがむしろ好ましいと思う。

  • どんな製品をつくるにせよ、組織というものはすべて判断と意思決定を生産する工場である。そして工場ならば、必ず製品の設計、製造、完成品それぞれの段階で、品質をチェックする手段を持っていなければならない。
  • 意思決定という製品の質的向上をめざすのであれば、どの段階についても効率改善を図らなければならない。有効なのは、品質チェックの定型化である
  • オフィスでの井戸端会議が質的に向上し語彙が的確になれば、よりよい意思決定に直結するだろう。意思決定者にとっては、自分自身の内なる疑念を想像するよりは、いまそこで噂話をしている人やすぐに批判しそうな人の声を想像するほうがたやすい。
  • 自分を批判する人々が正しい知識を身につけ、かつ公正であると信じられるなら、そして自分の下す決定が結果だけで判断されるのではなく、決断に至る過程も含めて判断されると信じられるなら、意思決定者はよりよい選択をするようになるだろう

組織というものはすべて判断と意思決定を生産する工場というフレーズがとても心に残った。 我々が意思決定者ではないときには、意思決定者が生み出す判断をチェックしなければいけないし、それができる組織を作ることもエンジニアリングの一部だということだ。 その判断がバイアスによって歪められたものでないことを、共に働く組織の一員として観察し、賛同し、ときに批判することが、その組織のクオリティを高めていくはず。


感想

上巻は結構時間がかかったけど、下巻は一気に読み終わった。 日頃から組織について考えることが多いので、後半の方はけっこうリアリティをもって読めた気がする。 全体を通して、ファスト&スローはエンジニアリングを生業とする人にぜひ薦めたい本だった。 組織の中で意思決定者と距離が近い人にこそ読んでほしい、意思決定者を支えるための本であった。

はてなブログにDouble OのAsk Meボタンを設置する

Double O というサービスがWeb ComponentsやPWAなどアグレッシブにWeb技術を取り入れていて応援したい気持ちなので、まずはユーザーとして使っていこうと思います。

ooapp.co

「よくある質問」がDouble Oの公式アカウントとして運用されてるの面白いですね https://ooapp.co/DoubleO

で、Ask Meボタンをどこに設置するのがよいかなーと考えた結果、ブログで記事を読んだあとに目に留まるところがいいんじゃないかと思い、はてなブログ上に設置してみました。 まさにこの記事の -> あたりに Ask Meのボタンが出ていると思います。

設置手順

Double Oのタグを入手する

Double Oのアカウントを作成し、ボタンのスクリプトを入手します。

f:id:lacolaco:20180410234637p:plain

スクリプトscriptタグと、oo-buttonタグの2つからなり、scriptタグで読み込まれるJavaScriptによりoo-buttonタグがWeb Componentsとして認識されるようになります。

headタグ内でoo-buttonタグを登録するためのスクリプトを読み込む

はてなブログの設定画面から詳細設定を開き、下の方にある head要素にタグを追加 のところにscriptタグだけを追加します。

f:id:lacolaco:20180410234518p:plain

これで、このはてなブログ内ではどこでもoo-buttonタグが使えるようになりました。

サイドバーにHTMLモジュールを配置

デザイン設定からサイドバーにHTMLモジュールを追加し、oo-buttonタグを配置するだけです。

f:id:lacolaco:20180410234836p:plain


質問だけでなく、マイクロナレッジの共有にも使えるサービスになっていくようなので、ブログにまとめるほどでもないようなこともぼちぼち書いていきつつ、マイクロナレッジのまとめをブログでサマライズするようなやりかたができるといいのかなーと思ってるところです。

ちなみに4/23に登壇する Web Components Cafe #2でもオンラインの質問場所としてDouble Oが使われますので、ぜひこちらもよろしくお願いします!

polymer-japan.connpass.com

amas.ooapp.co

すごいステマ記事みたいになってしまった。

Angular 頻出実装パターン その1

僕がAngularアプリケーションを書くときに頻出する実装パターンを紹介する記事です。続くかどうかは未定です。

onDestroy$

ngOnDestroyメソッドが呼び出されたタイミングでemitされるEventEmitterを作っておき、RxJSのtakeUntilパイプなどで使う実装パターン。 ngOnDestroyメソッド内でunsubscribeメソッドを呼び出すよりも宣言的で意味が取りやすいし、忘れにくい。

実装例はこんな感じ。ReactiveFormsModuleを使うときにvalueChangesに引っ掛けることが多い。

import { Component, OnDestroy, OnInit, EventEmitter, Output } from '@angular/core';
import { FormGroup, FormControl } from '@angular/forms';
import { takeUntil } from 'rxjs/operators';

@Component({
  selector: 'app-form',
  template: `
  <form [formGroup]="form">
    <input formControlName="name">
  </form>
  `
})
export class FormComponent implements OnDestroy {
  @Output() valueChange = new EventEmitter<any>();

  readonly form = new FormGroup({
    name: new FormControl(),
  });

  private readonly onDestroy$ = new EventEmitter();

  constructor() {
    this.form.valueChanges
      .pipe(
      takeUntil(this.onDestroy$),
    )
      .subscribe(value => {
        this.valueChange.emit(value);
      });
  }

  ngOnInit() {
    this.form.patchValue({
      name: 'Angular 5'
    });
  }

  ngOnDestroy() {
    this.onDestroy$.emit();
  }
}

https://stackblitz.com/edit/angular-vdnrbp

MaterialModule

Angular Materialのモジュールを束ねるための中間モジュールを作るパターン。 使っているモジュールが一箇所でわかるのと、TestBedに依存モジュールとして渡すのが楽になるので有用。 DI経由でグローバルに適用するAngular Materialの設定もここにまとめられるのでわかりやすい。

import { NgModule } from '@angular/core';
import {
  MatButtonModule,
  MatCardModule,
} from '@angular/material';
import { PortalModule } from '@angular/cdk/portal';
import { OverlayModule } from '@angular/cdk/overlay';

export const modules = [
  MatButtonModule,
  MatCardModule,
  OverlayModule,
  PortalModule,
];

@NgModule({
  imports: [...modules],
  exports: [...modules],
  providers: [
    {
      provide: MAT_LABEL_GLOBAL_OPTIONS,
      useValue: {
        float: 'always',
      },
    },
  ],
})
export class MaterialModule {}

safeHtmlパイプ

これはみんな書くでしょ、って気がする。与えられた文字列を安全なHTMLですよ、とマーキングしてAngularに渡すお仕事。

import { Pipe, PipeTransform } from '@angular/core';
import { DomSanitizer } from '@angular/platform-browser';

@Pipe({
  name: 'safeHtml',
})
export class SafeHtmlPipe implements PipeTransform {
  constructor(private sanitizer: DomSanitizer) {}

  transform(value: string): any {
    return this.sanitizer.bypassSecurityTrustHtml(value);
  }
}

provideXXX関数

サービスのプロバイダを関数としてエクスポートして、モジュール側で関数を呼び出すパターン。 何をprovideしているかがわかりやすい。 複数のサービスを一気にプロバイドする場合、特に順序が重要になるHTTP_INTERCEPTORSのようなmultiなプロバイダにおいて、モジュール側にそれを意識させずに済むのが気に入ってる。

export function provideHttpInterceptor() {
  return [
    {
      provide: HTTP_INTERCEPTORS,
      useClass: AuthorizationHeaderInterceptor,
      multi: true,
    },
    {
      provide: HTTP_INTERCEPTORS,
      useClass: CredentialInterceptor,
      multi: true,
    },
  ];
}
import { provideHttpInterceptor } from './config/http';

@NgModule({
  ...
  providers: [
    provideHttpInterceptor(),
  ],
})
export class CoreModule {}

書いてみるとそれほど種類がないなという気がしてきたので、次回は未定です。