Angular HTTP Tutorial メモ

https://angular.io/tutorial/toh-pt6

この記事はangular.ioにある一連のチュートリアルの最後の章だけ切り取っているので、これだけでもAngularのHttpまわりはなんとなくわかるけどヒーローがどうとかは意味がわからないと思うので https://angular.io/tutorial から https://angular.io/tutorial/toh-pt5 まで読むか、ng-japanさんがチュートリアルをベースに作成されたハンズオンの https://github.com/ng-japan/hands-on をやるといいかもしれない。

HTTPがメインなので、コンポーネントに関するところはスキップしている。

# HTTP

## Providing HTTP Services

`HttpModule` はAngularのコアモジュールではないので、 `@angular/http` を別途インストールしてアプリモジュールにimportする必要がある。

```ts
import { NgModule }      from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule }   from '@angular/forms';
import { HttpModule }    from '@angular/http';
 
import { AppRoutingModule } from './app-routing.module';
 
...
 
@NgModule({
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule,
    AppRoutingModule
  ],
  ...
})
export class AppModule { }
```

すると `Http` をサービスから使えるようになる。

```ts
export class MyService {
  constructor(private http: Http) { }

  fetchTodos() {
    return this.http.get('/api/todos');
  }
}
```

## Simulate the web API


ヒーローデータのリクエストを処理できるWebサーバーがないので、in-memory web APIでサービスをモックする。

https://github.com/angular/in-memory-web-api

```ts
import { NgModule }      from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule }   from '@angular/forms';
import { HttpModule }    from '@angular/http';
 
import { AppRoutingModule } from './app-routing.module';
 
// Imports for loading & configuring the in-memory web api
import { InMemoryWebApiModule } from 'angular-in-memory-web-api';
import { InMemoryDataService }  from './in-memory-data.service';
 
...
 
@NgModule({
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule,
    InMemoryWebApiModule.forRoot(InMemoryDataService),
    AppRoutingModule
  ],
  ...
})
export class AppModule { }
```

([json-server](https://github.com/typicode/json-server)あたり使ったほうが楽かつ今後も役に立ちそうな気も :innocent: )

## Heroes and HTTP

前章までのモックデータを使っていた箇所が以下のように書き換えられる。

```ts
getHeroes(): Promise<Hero[]> {
  return Promise.resolve(HEROES);
}
```

```ts
import { Headers, Http } from '@angular/http';

import 'rxjs/add/operator/toPromise';

@Injectable()
export class HeroService {

  private heroesUrl = 'api/heroes';  // URL to web api
 
  constructor(private http: Http) { }
 
  getHeroes(): Promise<Hero[]> {
    return this.http.get(this.heroesUrl)
       .toPromise()
       .then(response => response.json().data as Hero[])
       .catch(this.handleError);
  }
 
  private handleError(error: any): Promise<any> {
    console.error('An error occurred', error); // for demo purposes only
    return Promise.reject(error.message || error);
  }
...
}
```

Angularの `http.get` は `Observable` を返す。　Observableは非同期なデータフローを扱う強力なツールだが、それについては後述し、ここではPromiseに変換する。 :innocent: 

Observableと `.toPromise()` で変換したが、 `Observable` にデフォルトで生えているメソッドではない。toPromise以外のオペレータも同様で、次のようにRxJSからインポートする必要がある。なんでそんなめんどいことになってるのかも後述する。

```ts
import 'rxjs/add/operator/toPromise';
```

## Extracting the data in the `then` callback, Error Handling

`then` で成功時の、 `catch` で失敗時の処理をする。

```ts
getHeroes(): Promise<Hero[]> {
  return this.http.get(this.heroesUrl)
    .toPromise()
    .then(response => response.json().data as Hero[])
    .catch(this.handleError);
}
```

responseの `.json()` でbodyのJSONを取り出す。

```ts
private handleError(error: any): Promise<any> {
  console.error('An error occurred', error); // for demo purposes only
  return Promise.reject(error.message || error);
}
```

ここでは `Promise.reject` しているので、このメソッドを呼んだ側にはエラーが返る。

### (メモ)

JavaScriptの標準APIにXHRの後継のような[fetch API](https://fetch.spec.whatwg.org/)があり、 `@angular/http` はそれと似たAPIになっている。 __似ている__ ので違うところもある。

- `Response#json()` がfetchだとPromiseを返すが、AngularではPromiseじゃない
- `Response` とコードに書いてもTypeScriptが　`lib.dom.d.ts` とか `lib.es6.d.ts` に定義してある `Response` と解釈しておかしなことになることがある :innocent: 
    - `import { Http, Response } from '@angular/http';` でOK
    - Responseをimportせずに書いても(lib.dom.d.tsのResponseになってしまっていても)、↓みたいにちゃんと型を明記してればTypeScriptがなんかおかしいぞと言ってくるので気付ける

```ts
/*
[ts]
Type 'Observable<Response>' is not assignable to type 'Observable<Response>'. Two different types with this name exist, but they are unrelated.
  Type 'Response' is not assignable to type 'Response'. Two different types with this name exist, but they are unrelated.
    Property 'body' is missing in type 'Response'.
*/
fetch(): Observable<Response> {
  return this.http.get('api/heroes');
}
```

## Get hero by id

`api/hero/11` みたいなAPIを叩きたいときは、メソッドの引数にidを持ってそれをURLに入れてやれば良い。

```ts
getHero(id: number): Promise<Hero> {
  const url = `${this.heroesUrl}/${id}`;
  return this.http.get(url)
    .toPromise()
    .then(response => response.json().data as Hero)
    .catch(this.handleError);
}
```

## Updating hero details

`PUT /api/hero/3` のようなPUTメソッドは `http.put` で行う。putメソッドのシグネチャは `put(url: string, body: any, options?: RequestOptionsArgs): Observable<Response>;`

```ts
private headers = new Headers({'Content-Type': 'application/json'});

update(hero: Hero): Promise<Hero> {
  const url = `${this.heroesUrl}/${hero.id}`;
  return this.http
    .put(url, JSON.stringify(hero), {headers: this.headers})
    .toPromise()
    .then(() => hero)
    .catch(this.handleError);
}
```

POST, DELETEも同様

```ts
create(name: string): Promise<Hero> {
  return this.http
    .post(this.heroesUrl, JSON.stringify({name: name}), {headers: this.headers})
    .toPromise()
    .then(res => res.json().data as Hero)
    .catch(this.handleError);
}

delete(id: number): Promise<void> {
  const url = `${this.heroesUrl}/${id}`;
  return this.http.delete(url, {headers: this.headers})
    .toPromise()
    .then(() => null)
    .catch(this.handleError);
}
```

## Observables

Httpのメソッドは `Observable<Response>` を返す。これまではPromiseに変換してきたが、この章ではどのように、いつ、なぜ直接Observableを返すのかについて示す。

### Background

Observableはmap,filterみたいな配列に似たオペレータをたくさん持った非同期を強力に扱えるライブラリで、Angularのコアでもよく使われている。
HttpのGETみたいな、一つのデータを取っておしまいみたいなケースではPromiseへの変換もよい選択と言える。
しかし、リクエストをキャンセルして別のリクエストを投げるといった操作はPromiseでは難しいが、Observableでは簡単に行える。

ヒーローの検索は以下の通り

```ts
import { Injectable } from '@angular/core';
import { Http }       from '@angular/http';
 
import { Observable }     from 'rxjs/Observable';
import 'rxjs/add/operator/map';
 
import { Hero }           from './hero';
 
@Injectable()
export class HeroSearchService {
 
  constructor(private http: Http) {}
 
  search(term: string): Observable<Hero[]> {
    return this.http
      .get(`api/heroes/?name=${term}`)
      .map(response => response.json().data as Hero[]);
  }
}
```

`toPromise()` していないので、戻り値の型が `Observable` になっている。 `map` というのがRxJSのオペレーターの一つで、　`Observable<Response>` から `Observable<Hero[]>` に変換している。
コンポーネントからこのメソッドを使う。

```html:hero-search.component.html
<div id="search-component">
  <h4>Hero Search</h4>
  <input #searchBox id="search-box" (keyup)="search(searchBox.value)" />
  <div>
    <div *ngFor="let hero of heroes | async"
         (click)="gotoDetail(hero)" class="search-result" >
      {{hero.name}}
    </div>
  </div>
</div>
```

```ts:hero-search.component.ts
export class HeroSearchComponent implements OnInit {
  heroes: Observable<Hero[]>;
  private searchTerms = new Subject<string>();

  // Push a search term into the observable stream.
  search(term: string): void {
    this.searchTerms.next(term);
  }

  ngOnInit(): void {
    this.heroes = this.searchTerms
      .debounceTime(300)        // wait 300ms after each keystroke before considering the term
      .distinctUntilChanged()   // ignore if next search term is same as previous
      .switchMap(term => term   // switch to new observable each time the term changes
        // return the http search observable
        ? this.heroSearchService.search(term)
        // or the observable of empty heroes if there was no search term
        : Observable.of<Hero[]>([])
      )
      .catch(error => {
        // TODO: add real error handling
        console.log(error);
        return Observable.of<Hero[]>([]);
      });
  }
}
```

### (メモ)

`ngOnInit` 以外の説明

- inputのkeyupイベントで、コンポーネントのsearchメソッドが実行される
    - メソッドの引数にはinput.valueが渡る。 `#searchBox` でテンプレートの参照を取得している。
- searchメソッドは、inputに入力されている値で `Subject#next` メソッドを実行する
    - SubjectはObservableの派生クラスで、 `next` メソッドが生えているのが大きな違い。 `next` を実行することで、Observableストリームにデータを流すことができる。この特徴のために、よく状態を表現するのに使われる。ここでは「inputに入力された文字列」の状態を表現している。

`ngOnInit` の説明

- `this.searchTerms` にいろいろしている。つまりinputの文字列が変化するとここに書いてある処理がイベントドリブンに動く。
- 色々した結果が `heroes: Observable<Hero[]>` に入れている。この `heroes` をテンプレートで `<div *ngFor="let hero of heroes | async">` しているので、まとめると、inputに文字列を入れるとなんやかんやあって結果のリストが表示されるっぽいことがわかる。
- `debounceTime(300)`  300 msは処理を待つ。inputに文字が一文字入るごとにバックエンドにリクエストを投げるとエライことになるので、`searchTerms`ストリームにイベントが最後に流れてから300ms何もなかったら、後続に値を流す。
    - [API document](http://reactivex.io/rxjs/class/es6/Observable.js~Observable.html#instance-method-debounceTime)
- `distinctUntilChanged()` 前に流れたイベントと同じなら、後続に流さないようにする。同じ検索単語のリクエストを投げても結果は同じなので、ここで省いている。
    - [API document](http://reactivex.io/rxjs/class/es6/Observable.js~Observable.html#instance-method-distinctUntilChanged)
- `switchMap` 新しいObservableにスイッチする。ここまでは検索単語のストリームだったのを、検索結果のストリームにスイッチしている。検索のリクエストを投げる前に、文字列が空であれば空の結果をその場で生成している。 `switchMap`,  `concatMap` , `mergeMap` とか似たオペレータが多いので使い分けが難しい。　 `switchMap` は最新の検索結果のみを後続に流す。古い検索結果のObservableはキャンセルし、破棄する。（キャンセルはそういう実装をしないとキャンセルにならず、この例だと単に古い結果を破棄するだけらしい）
    - [API document](http://reactivex.io/rxjs/class/es6/Observable.js~Observable.html#instance-method-switchMap)
- `catch` Observableのエラーハンドリングをする。このメソッドのreturnで `Observable.of([])` としているので、もしエラーが起こった際は空のリストが結果として返るようになっている。 `throw new Error('...')` とすれば後続にエラーが流れる。
    - [API document](http://reactivex.io/rxjs/class/es6/Observable.js~Observable.html#instance-method-catch)

(ドキュメントにも似たような説明あるの気づかずに書いてしまったので気になる人はドキュメントも読んで :innocent: )

## Import RxJS operators

Observableに生えてるメソッドやObservableの派生クラスを使うときは別途importする。パターンとして大体3つに分けられる。

```ts
// Observableやその派生クラス
import { Observable }        from 'rxjs/Observable';
import { Subject }           from 'rxjs/Subject';

// Observableの静的メソッド
import 'rxjs/add/observable/of';
import 'rxjs/add/observable/create';

// Observableのオペレーター
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/distinctUntilChanged';
```

## Home Stretch

この章でやったこと

- HTTPモジュールからインポートした
- web APIを使ってHeroServiceをリファクタした
- get(), post(), put(), delete()メソッドを使った
- コンポーネントからヒーローを追加、編集、削除できるようにした(この記事では全部スキップした :innocent: )
- in-memory-web-apiを使った
- Observableがチョットワカッタ

https://angular.io/tutorial/toh-pt6