Angluar2のクイックスタートとチュートリアルを実施 - その7

前回の投稿[Angluar2のクイックスタートとチュートリアルを実施 - その6](http://qiita.com/gambare/items/3585c57860e9f15120ff)の続きです。
前回作成したソースを使用します。

本章ではhttpを通してリモートサーバーのweb APIを呼び出し、データの取得・追加・削除する方法を学びます。

本章もかなり長いです...

### 本投稿の参照元(英語)

[HTTP - ts](https://angular.io/docs/ts/latest/tutorial/toh-pt6.html)

### 本章で学ぶこと

前述のほか、以下を学びます。

 - インメモリweb APIの作り方(深入りはしません)
 - Observableの使い方

尚、本章のメインテーマはAngularのHTTPライブラリの紹介ですので、インメモリwebAPIについての説明は主題ではありません。より多くインメモリwebAPIについて学習したい場合、[HTTP client chapter](https://angular.io/docs/ts/latest/guide/server-communication.html#!#in-mem-web-api)(英語)を参照ください。インメモリwebAPIはデモンストレーションのために使用しています。何か替わりになるリモートサーバをお持ちであれば学習不要です。

### 実行結果

本章を完了すると以下リンク先のようなものが出来上がります。
ヒーローの追加・削除機能などが実装されています。
[live-examples](https://angular.io/resources/live-examples/toh-6/ts/plnkr.html)

### 事前準備

以下のコマンドによりサーバの起動を行います。プログラムの変更が即座にブラウザに反映されます。(サーバを落としていなければ不要です。)

```bash
cd angular2-tour-of-heroes
npm start
```

# HTTPサービスの提供

`HttpModule`はAngularのcoreモジュールには含まれていません。オプションのアプローチであり、`@angular/http`として、Angular npmパッケージに存在します。
既に`systemjs.config`で同モジュールを取得する設定を行なっているので、すぐにインポートできます。

## HTTPサービスの登録

`@angular/http`の`HttpModule`は、HTTPサービスのすべてを提供します。このサービスはアプリのどこからでもアクセスできます。
`AppModule`の`imports`に`HttpModule`を追加します。

```ts:app/app.module.ts
import { NgModule }      from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule }   from '@angular/forms';
import { HttpModule }    from '@angular/http';
import { AppComponent }         from './app.component';
import { DashboardComponent }   from './dashboard.component';
import { HeroesComponent }      from './heroes.component';
import { HeroDetailComponent }  from './hero-detail.component';
import { HeroService }          from './hero.service';
import { routing }              from './app.routing';
@NgModule({
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule,
    routing
  ],
  declarations: [
    AppComponent,
    DashboardComponent,
    HeroDetailComponent,
    HeroesComponent,
  ],
  providers: [
    HeroService,
  ],
  bootstrap: [ AppComponent ]
})
export class AppModule {
}
```

# 模擬的web API

現在のアプリは開発中で、製品にするには程遠い状況です。ヒーロー一覧を扱うwebサーバもありません。そのため偽のサーバを立てます。
_インメモリWeb API_により、HTTPクライアントを偽装し、モックサービスのデータを取得・保存します。

以下が、偽装版の`app/app.module.ts`です。

> <b>[注意!]</b>インメモリweb APIのパッケージ名称が`angular2-in-memory-web-api`から`angular-in-memory-web-api`に変更されました。(2.0.1から？)[クイックスタート](http://qiita.com/gambare/items/e52b49c09890e1180610#ステップ1プロジェクトの作成と設定)のpackage.jsonにて指定したインメモリweb APIの名称に応じて、インポートするパッケージ名を変更するか、再度`package.json`を更新し、古いライブラリを削除した後、`npm install`コマンドを実行し、最新のライブラリを使用してください。

```ts:app/app.module.ts
import { NgModule }      from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule }   from '@angular/forms';
import { HttpModule }    from '@angular/http';

// Imports for loading & configuring the in-memory web api
import { InMemoryWebApiModule } from 'angular-in-memory-web-api';
import { InMemoryDataService }  from './in-memory-data.service';

import { AppComponent }         from './app.component';
import { DashboardComponent }   from './dashboard.component';
import { HeroesComponent }      from './heroes.component';
import { HeroDetailComponent }  from './hero-detail.component';
import { HeroService }          from './hero.service';
import { routing }              from './app.routing';

@NgModule({
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule,
    InMemoryWebApiModule.forRoot(InMemoryDataService),
    routing
  ],
  declarations: [
    AppComponent,
    DashboardComponent,
    HeroDetailComponent,
    HeroesComponent,
  ],
  providers: [
    HeroService,
  ],
  bootstrap: [ AppComponent ]
})
export class AppModule {
}
```

`InMemoryWebApiModule`はデフォルトのhttpクライアントのバックエンドを置換し、_インメモリweb API拡張サービス_を持つ拡張サーバにします。

```ts:
InMemoryWebApiModule.forRoot(InMemoryDataService),
```
`forRoot`設定メソッドは以下の`app/in-memory-data.service.ts`の作成により、インメモリデータベースを実装する`InMemoryDataService`クラスを取得します。

```ts:app/in-memory-data.service.ts
import { InMemoryDbService } from 'angular-in-memory-web-api';
export class InMemoryDataService implements InMemoryDbService {
  createDb() {
    let heroes = [
      {id: 11, name: 'Mr. Nice'},
      {id: 12, name: 'Narco'},
      {id: 13, name: 'Bombasto'},
      {id: 14, name: 'Celeritas'},
      {id: 15, name: 'Magneta'},
      {id: 16, name: 'RubberMan'},
      {id: 17, name: 'Dynama'},
      {id: 18, name: 'Dr IQ'},
      {id: 19, name: 'Magma'},
      {id: 20, name: 'Tornado'}
    ];
    return {heroes};
  }
}
```
このファイルの作成によりモックデータを扱っていた`app/mock-heroes.ts`は削除できます。

# ヒーロー一覧とHTTP
`HeroService`は以下の様に実装しています。

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

プロミスの結果をモックのヒーロー配列と一緒に返しています。もともと、ただ単にヒーロー一覧を返せばいいため、プロミスで返すことは過剰な対応でしたが、これはHTTPクライアントから非同期にデータを取得することを予定していたためです。今がそのときですので、`getHeroes()`メソッドをHTTPを使用するように変更しましょう。

```ts:app/hero.service.ts
  private heroesUrl = 'app/heroes';  // web apiへのURL

  constructor(private http: Http) { }

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

インポートは以下の様に変更します。

```ts:app/hero.service.ts
import { Injectable }    from '@angular/core';
import { Headers, Http } from '@angular/http';

import 'rxjs/add/operator/toPromise';

import { Hero } from './hero';
```

ブラウザ更新により、ヒーローデータは正常にモックサーバから取得できます。


## HTTPプロミス

まだ`getHeroes()`は、プロミスを返すメソッドですがその方法は違います。Angularの`http.get`は、RxJSの`Observable`を返します。_Observable_は非同期通信を扱うための有力な方法です。本章の後半で学びます。
`Observable`から`Promise`への変換は、`toPromise`演算子により行います。

```ts:
.toPromise()
```
残念ながら、Angularの`Observable`はRxJSのものと異なり、`.toPromise`演算子を持っておらず、ベアボーンな実装です。
Angularの`Observable`で`toPromise`演算子のような便利な機能を使うためには、演算子を独自に加える必要がありますがRxJSライブラリから取得するだけなので簡単です。

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

## _then_コールバックによるデータの取得

プロミスの`then`コールバックでは、HTTPの`Response`の`json`メソッドを呼び出し、データを取り出せます。

```ts:
.then(response => response.json().data as Hero[])
```

JSONのレスポンスは単一の`data`プロパティを返し、`data`プロパティはヒーロー配列を持ちます。この配列を解決済みのプロミスとして返します。

注意点：サーバからデータを取得するとき、今回のインメモリweb APIは`data`プロパティのみを返しますが、これはweb APIにより変わります。必要に応じコードを改修してください。

## エラーハンドリング

`getHeroes()`メソッドの最後に、`catch`でサーバのデータ取得失敗などをエラーハンドリングしています。

```ts:
.catch(this.handleError);
```

ここは重要なステップです。HTTPの想定を超えて頻繁に発生するfailureを予想しなければなりません。

```ts:
private handleError(error: any): Promise<any> {
  console.error('An error occurred', error); // デモの目的のみ
  return Promise.reject(error.message || error);
}
```

このデモでは、コンソールにログを出しますが、本来はもっとベターに扱うべきです。また、エラーフォームを呼び出し元へプロミスの破棄で返し、呼び出し元では適切にユーザにエラーメッセージを表示します。

## `getHeroes()` APIは変更しない

`getHeroes()`メソッドを大きく変えましたが、外見は変わっておらず、まだプロミスを返します。そのため、このメソッドを呼び出す他のメソッドは変更の必要はありません。
それではヒーロー詳細を更新したときに何が起こるかを見ていきましょう。

# ヒーロー詳細の更新

現在ヒーロー詳細は更新できますが、`Back`ボタンを押すとその更新内容が失われます。以前は問題ありませんでした。アプリがモックデータのヒーローリストを使っていたときは、アプリ全体のヒーローオブジェクトをダイレクトに変更していました。現在はサーバからデータを取得しているので、永続的に変更(変更の保持)したい場合、サーバに書き込む必要があります。

## ヒーロー詳細の保存

ヒーロー詳細を失うことなく保存する様にしましょう。[save]ボタンをヒーロー詳細テンプレートに追加します。saveボタンには(click)イベントをバインディングし、コンポーネントの`save()`メソッドを呼び出す様にします。

```html:app/hero-detail.component.html
<button (click)="save()">Save</button>
```

`save`メソッドは`heroService`の`update()`メソッドを使用し、ヒーローの名前を変更します。同時に一つ前のビューに戻ります。

```ts:app/hero-detail.component.ts
save(): void {
  this.heroService.update(this.hero)
    .then(this.goBack);
}
```

## ヒーローサービスの`update()`メソッド

`update()`メソッドは、`getHeroes()`メソッドに似ていまが、HTTP _put_により永続的な変更を行います。

```ts:app/hero.service.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);
}
```

URL用に変更したヒーローIDにより、サーバがどのヒーローを更新すべきかを識別します。putのボディ部分には`JSON.stringify`でヒーローをjson文字列でエンコーディングしたものです。リクエストヘッダによりボディのコンテントタイプを`application/json`と識別させます。

ブラウザを更新し、ヒーロー名を変更してください。変更が永続化されます。

# ヒーローの追加

ヒーローを追加するためには、ヒーロー名が必要です。ヒーロー名を入れる要素とボタンを用意します。ヒーロー一覧のテンプレートの見出しの後に以下を追加します。

```html:app/heroes.component.html
<div>
  <label>Hero name:</label> <input #heroName />
  <button (click)="add(heroName.value); heroName.value=''">
    Add
  </button>
</div>
```

クリックイベントのレスポンスでコンポーネントのクリックハンドラー(`add()`メソッド)を呼びます。また次のヒーロー名を入力できるように、入力フィールドを空にします。

```ts:app/heroes.component.ts
add(name: string): void {
  name = name.trim();
  if (!name) { return; }
  this.heroService.create(name)
    .then(hero => {
      this.heroes.push(hero);
      this.selectedHero = null;
    });
}
```

`add()`メソッドでは、名前が空白でない場合に入力された名前のヒーローをサービスに渡すことにより、配列に新しいヒーローが追加されます。ここで呼び出す`create()`メソッドを`HeroService`クラスに実装します。

```ts:app/hero.service.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)
    .catch(this.handleError);
}
```

ブラウザ更新により、ヒーローが作成できるようになります。

# ヒーローの削除

ヒーロー一覧の各ヒーローに削除ボタンを用意し、ヒーローを削除できるようにします。
各ヒーロー名の右側で繰り返す`<li>`タグにボタン[x]を追加します。

```html:
<button class="delete"
  (click)="delete(hero); $event.stopPropagation()">x</button>
```
結果、`<li>`要素は以下の様になります。

```html:app/heroes.component.html
  <li *ngFor="let hero of heroes" (click)="onSelect(hero)"
      [class.selected]="hero === selectedHero">
    <span class="badge">{{hero.id}}</span>
    <span>{{hero.name}}</span>
    <button class="delete"
      (click)="delete(hero); $event.stopPropagation()">x</button>
  </li>
```

加えて、ボタンクリック時に呼ばれるコンポーネントの`delete`メソッドでは、クリックイベントの伝道を抑止します。この理由は、削除される予定のヒーローの`<li>`要素がクリックされない様にするためです。`delete`ハンドラはちょっと手がこみます。

```ts:app/heroes.component.ts
delete(hero: Hero): void {
  this.heroService
      .delete(hero.id)
      .then(() => {
        this.heroes = this.heroes.filter(h => h !== hero);
        if (this.selectedHero === hero) { this.selectedHero = null; }
      });
}
```
 
ヒーローデータの削除はサービス側で行いますが、コンポーネント側では画面表示を更新する(削除されたヒーローを非表示にする)責任があります。配列からの削除(`filter`)と、必要に応じて選択中ヒーローの選択解除(`null`代入)が必要です。ヒーローエンティティの右端に削除ボタンを配置するため、CSSを更新します。

```css:app/heroes.component.css
button.delete {
  float:right;
  margin-top: 2px;
  margin-right: .8em;
  background-color: gray !important;
  color:white;
}
```

## ヒーローサービスの`delete()`メソッド

HTTPの`delete()`メソッドにより、ヒーローをサーバから削除します。

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

ブラウザ更新して、削除機能を確認して見てください。

# Observables

httpサービスのメソッドは、HTTP `Response`オブジェクトの`Observable`を返します。現在`HeroService`は`Observable`を(`toPromise`により)`Promise`に変換して返していますが、ここでは`Observable`をそのまま返す方法および、なぜそうするのかを学びます。

## バックグラウンド

_observable_は配列の様に処理ができるストリームです。
Angularコアは_observable_のベース部分をサポートし、[RxJS Observables](http://reactivex.io/rxjs/)にある演算子などによる拡張をサポートしています。ここでは軽く説明します。
`HeroService`は`http.get`の結果の`Observable`にすぐに`toPromise`演算子をつなぎ、`Promise`を取得し、それを返します。普通`http.get`は単一のデータを取得するため、`Promise`に変換することは良い選択ですが、リクエストは必ずしも一つずつ処理するわけではありません。利用方法によっては、最初は単一リクエストだったものが、サーバがレスポンスを返す前に次のリクエストが送られることにより、単一リクエストでなくなる可能性があります。この様に連続した_リクエスト-キャンセル-新しいリクエスト_は`Promise`で処理することが難しいですが、`Observable`では容易です。

`HeroSearchService`の作成を開始します。これは検索クエリをサーバAPIに送信します。

```ts:app/hero-search.service.ts
import { Injectable }     from '@angular/core';
import { Http, Response } from '@angular/http';
import { Observable } from 'rxjs';
import { Hero }           from './hero';
@Injectable()
export class HeroSearchService {
  constructor(private http: Http) {}
  search(term: string): Observable<Hero[]> {
    return this.http
               .get(`app/heroes/?name=${term}`)
               .map((r: Response) => r.json().data as Hero[]);
  }
}
```
`HeroSearchService`の`http.get()`は`HeroService`のものに似ていますが、URLはクエ文字列(`?name=${term}`)があります。もう一つの違いは、`toPromise`を呼ばず、_observable_をそのまま返している事です。

## HeroSearchComponent
`HeroSearchComponent`を作り、`HeroSearchService`を呼びます。
テンプレートはシンプルに、検索用の入力テキストボックスと、検索により一致したヒーローの一覧を表示する欄です。

```html:app/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>
```
このコンポーネントのスタイルシートを作ります。

```css:app/hero-search.component.css
.search-result{
  border-bottom: 1px solid gray;
  border-left: 1px solid gray;
  border-right: 1px solid gray;
  width:195px;
  height: 20px;
  padding: 5px;
  background-color: white;
  cursor: pointer;
}
#search-box{
  width: 200px;
  height: 20px;
}
```

サーチボックスに文字を入れた時に、_keyup_イベント[^keyup]がバインドされているコンポーネントの`serach()`メソッドを、入力値とともに呼び出します。
`*ngFor`はコンポーネントの`heroes`プロパティの_hero_オブジェクトに対してループしますが、`heroes`プロパティはヒーロー配列というより、_Observable_なヒーロー配列なので`async`パイプ(`AsyncPipe`)を通してでないと何もできません。`async`パイプは`Observable`と`*ngFor`の結果を支援します。
[^keyup]: keyupイベントは、キーボードを押す -> 離す により実行されます
`HeroSearchComponent`クラスとメタデータを以下の様に作成します。要点を後ほど説明します。

```ts:app/hero-search.component.ts
import { Component, OnInit } from '@angular/core';
import { Router }            from '@angular/router';
import { Observable }        from 'rxjs/Observable';
import { Subject }           from 'rxjs/Subject';
import { HeroSearchService } from './hero-search.service';
import { Hero } from './hero';
@Component({
  moduleId: module.id,
  selector: 'hero-search',
  templateUrl: 'hero-search.component.html',
  styleUrls: [ 'hero-search.component.css' ],
  providers: [HeroSearchService]
})
export class HeroSearchComponent implements OnInit {
  heroes: Observable<Hero[]>;
  private searchTerms = new Subject<string>();
  constructor(
    private heroSearchService: HeroSearchService,
    private router: Router) {}
  // 検索条件をobservableストリームに入れる。
  search(term: string): void {
    this.searchTerms.next(term);
  }
  ngOnInit(): void {
    this.heroes = this.searchTerms
      .debounceTime(300)        // 300ms イベントを止める
      .distinctUntilChanged()   // 次の検索条件が以前と同じなら無視する
      .switchMap(term => term   // 新しいobservableに変換する
        // http検索observableを返す
        ? this.heroSearchService.search(term)
        // または条件がないとき、空のヒーローのobservableを返す
        : Observable.of<Hero[]>([]))
      .catch(error => {
        // TODO: 現実のエラーハンドリングを行う
        console.log(error);
        return Observable.of<Hero[]>([]);
      });
  }
  gotoDetail(hero: Hero): void {
    let link = ['/detail', hero.id];
    this.router.navigate(link);
  }
}
```
## 検索条件

まずは、`searchTerms`にフォーカスを合わせます。

```ts:
private searchTerms = new Subject<string>();

// 検索条件をobservableストリームに入れる。
search(term: string): void {
  this.searchTerms.next(term);
}
```

`Subject`は、_observable_イベントストリームを作成します。`searchTerms`は、`Observable`文字列を作成します。ヒーロー名検索フィルタの判定基準(criteria)です。
`search`を呼びだし、`next`により、新しい文字列を`subject`の`Observable`ストリームに格納します。

## ヒーロー一覧プロパティの初期化 (NGONINIT)

`Subject`は、`Observable`です。検索条件のストリームを`Hero`配列に変え、`heroes`プロパティに代入します。

```ts:
heroes: Observable<Hero[]>;

ngOnInit(): void {
  this.heroes = this.searchTerms
    .debounceTime(300)        // 300ms イベントを止める
    .distinctUntilChanged()   // 次の検索条件が以前と同じなら無視する
    .switchMap(term => term   // 新しいobservableに変換する
      // http検索observableを返す
      ? this.heroSearchService.search(term)
      // または条件がないとき、空のヒーローのobservableを返す
      : Observable.of<Hero[]>([]))
    .catch(error => {
      // TODO: 現実のエラーハンドリングを行う
      console.log(error);
      return Observable.of<Hero[]>([]);
    });
}
```

キー入力が直接`HeroSearchService`を呼び出すとHTTPリクエストが嵐の様に発生します。これは良い手段ではありません。サーバリソース料や、ネットワーク使用料を払いたくありません。`Observable`の演算子群を`Observable`文字列をつなぐことで、リクエストの数を減らします。`HeroSearchService`に対して少ないリクエスト行い、タイムリーに結果を返します。やり方は以下の通りです。

 - `debounceTime(300)`: 最後の文字列の後から300ms、新しい文字列イベントの流れを止める。

 - `distinctUntilChanged`: フィルターのテキストが変わったときのみ、リクエストを送る。同じ検索条件で繰り返しリクエストしても無駄。

 - `switchMap`: 検索条件を`debounce` と `distinctUntilChanged`に通して、検索サービスを呼び出す。前回以前に検索した全ての_observable_の取り消し、破棄を行い、最後の検索結果の_observable_のみを返す。

> [switchMap operator](http://www.learnrxjs.io/operators/transformation/switchmap.html)[^switchMap](英語)は、とても賢いです。
各キーイベントが`http`イベントを呼び出します。300msのリクエストの間に複数のHTTPリクエストを飛ばすことができます。`switchMap`は一番新しい`http`メソッドからobservableを返す間、一番古いリクエストを保持します。検索テキストが空の場合、`http`メソッドの呼び出しを短絡に行い、空の配列を持つobservableを返します。実は`HeroSearchService`呼び出しをキャンセルした場合、未解決のHTTPリクエストを中止しません。このトピックは別の日に。今は、期待しない結果を破棄していることに満足しましょう。

 - `catch`は失敗した`observable`を横取りします。今回コンソールにエラーを出力しましたが、現実にはうまくやってください。今回は空配列を`observable`に入れ検索結果をクリアしています。

[^switchMap]: 以前は"flatMapLatest"と呼ばれていたそうです。


## `RxJS`演算子のインポート

先述の通り、Angularの基本の`Observable`の実装には、RxJSの演算子はありません。インポートして拡張する必要があります。演算子をインポートすることで`Observable`を拡張しますが、以下の例では異なったアプローチにより、RxJSの演算子の組み合わせた一つのファイルをインポートし`Observable`の拡張します。

```ts:app/rxjs-extensions.ts
// Observable class extensions
import 'rxjs/add/observable/of';
import 'rxjs/add/observable/throw';

// Observable operators
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/distinctUntilChanged';
import 'rxjs/add/operator/do';
import 'rxjs/add/operator/filter';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/switchMap';
```

このファイルを`AppModule`でimportし、全ての演算子を一度にロードします。

```ts:app/app.module.ts
import './rxjs-extensions';
```


## ダッシュボードに検索コンポーネントを追加

`DashboardComponent`のテンプレートに検索機能を追加します。

```html:app/dashboard.component.html
<h3>Top Heroes</h3>
<div class="grid grid-pad">
  <div *ngFor="let hero of heroes" (click)="gotoDetail(hero)" class="col-1-4">
    <div class="module hero">
      <h4>{{hero.name}}</h4>
    </div>
  </div>
</div>
<hero-search></hero-search>
```
最後に、`HeroSearchComponent`をインポートし、`declarations`配列に追加します。

```ts:app/app.module.ts
  declarations: [
    AppComponent,
    DashboardComponent,
    HeroDetailComponent,
    HeroesComponent,
    HeroSearchComponent
  ],
```

ブラウザを更新しダッシュボードに移動しテキストを入力すると、以下の様に表示されます。
![aaa](https://angular.io/resources/images/devguide/toh/toh-hero-search.png)

# アプリケーションの構造とコード
チュートリアル実施により、出来上がったアプリのファイルおよびコードは、[公式](https://angular.io/docs/ts/latest/tutorial/toh-pt6.html#!#application-structure-and-code)にあります。ご参考願います。

----
以上でチュートリアルは終了です。お疲れ様でした。

----