Djangoroidの奮闘記

python,django,angularJS1~三十路過ぎたプログラマーの奮闘記

Angular2 The Tour of Heroes tutorial に挑戦してみる 4

SERVICES

https://angular.io/docs/ts/latest/tutorial/toh-pt4.html

Creating a hero service

In this page, you’ll move the hero data acquisition business to a single service that provides the data and share that service with all components that need the data.

Create the HeroService

  • Create a file in the app folder called hero.service.ts.

  • The naming convention for service files is the service name in lowercase followed by .service. For a multi-word service name, use lower dash-case. For example, the filename for SpecialSuperHeroService is special-super-hero.service.ts.

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

@Injectable()
export class HeroService {

}

Injectable services

  • Injecttable functionをimportして、@Injectable()デコレータをapplyさせる
@Injectable()
export class HeroService {
  getHeroes(): void {} // stub
}
  • The HeroService could get Hero data from anywhere—a web service, local storage, or a mock data source

Move the mock hero data

  • app.component.ts から、HEROESをカットして、mock-heroes.tsに貼り付ける
import { Hero } from './hero';

export const HEROES: Hero[] = [
  { 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' }
];
  • exportは、変数にも適用できるんやな。classだけではなくて。
  • HEROES constは、exportされているので、HeroServiceなどどこからでもimportができる
モックについては以下の記事が分かりやすかったです。
http://uehaj.hatenablog.com/entry/20090427/1240815860

モックについて言うと「オブジェクト間の相互作用を見るためのテスト」をするときに用いるのがモック。以上。それ以上でもそれ以下でもない。便利さの度合いも試験の手軽さもスピードも関係ない(直交する問題)。

例えば、ある「細胞」に対して試験をするとします。このとき:

刺激を与え、その細胞の1個の内部の状態がどう変化するか、を見るのが、状態中心のテスト。酸性度が上がるのか、ミトコンドリアが分裂するのか、など。いわゆる状態中心のテスト、普通のユニットテストです。
刺激を与え、その刺激をきっかけとして、その細胞が他に対してどういう影 響を及ぼすのか、シナプス経由で電気信号を発するのか、化学物質を放出するのかを調べる相互作用の試験。細胞内部には興味ありませんし調べません。モックを用いたテストがこっちです。

モックは後者のためにしか使えないし、モックのあらゆる機能がそのために設計されているのが分かります。
  • app.component.ts から、HEROESをカットしたので、add an uninitialized heroes propertyをする。Heroのリストが入ることを想定して、Hero[]というHero classのリストを型として定義しておくのか?
export class AppComponent {
  title = 'Tour of Heroes';
  heroes: Hero[];
  selectedHero: Hero;
  onSelect(hero: Hero): void {
    this.selectedHero = hero;
  }
}

Return mocked hero data

  • HeroServiceに、HEROESをimportして、getHeroes()メソッドからそれを取得できるように設定する
import { Injectable } from '@angular/core';

import { Hero } from './hero';
import { HEROES } from './mock-heroes';

@Injectable()
export class HeroService { // HeroService classをexportする
  getHeroes(): Hero[]{ // getHeroesメソッドを定義する //データの型定義は、Hero[](Hero classのリスト)
    return HEROES; // getHeroesにアクセスしたら、HEROES(from mock-heroes)を返す
  }
}

Import the hero service

import { HeroService } from './hero.service';
  • これだけか、便利だな(^^)

Don’t use new with the HeroService

heroService = new HeroService(); // don't do this このやり方は、NG!!

Inject the HeroService

  • new を使う代わりに、Add a constructor that also defines a private property.と、Add to the component's providers metadata.で、HeroServiceを利用する。
constructor(private heroService: HeroService) { }
  • constructor は、コンストラクタとは、クラスからオブジェクトを作成した際に、自動的に実行されるメソッドのことで、メンバ変数の初期化などの主に行います。ということらしい。python/djangoでいうと、__init__とか、初期化メソッドのことかな。

  • The constructor itself does nothing. The parameter simultaneously defines a private heroService property and identifies it as a HeroService injection site.(コンストラクタ自身は特に何もしない。heroServiceのprivate変数の定義とそれがHeroService injection siteであることをidentifyするために。)

  • 上記のコードは、AppComponent に追記する。

export class AppComponent {
  title = 'Tour of Heroes';
  heroes: Hero[];
  selectedHero: Hero;

  constructor(private HeroService: HeroService){}

  onSelect(hero: Hero): void {
    this.selectedHero = hero;
  }
}
  • なんで、ここに挿入するのかが、よくわからない。。。

  • ERROR Error: No provider for HeroService!が、表示されるので、providerを設定する

providers: [HeroService]
  • The providers array tells Angular to create a fresh instance of the HeroService when it creates an AppComponent、providers 配列は、Angular に、AppComponetを作成したときに、create fresh instance of the HeroService をするように命令する。

getHeroes() in the AppComponent

  • AppComponentに、getHeroesを設定する
export class AppComponent {
  title = 'Tour of Heroes';
  heroes: Hero[];
  selectedHero: Hero;

  constructor(private heroService: HeroService){}

  getHeroes(): void {
      this.heroes = this.heroService.getHeroes();
    }

  onSelect(hero: Hero): void {
    this.selectedHero = hero;
  }
}

The ngOnInit lifecycle hook

  • You might be tempted to call the getHeroes() method in a constructor, but a constructor should not contain complex logic, especially a constructor that calls a server, such as as a data access method. constructorで、getHeroes()読んだほうがいいんじゃないかと思うかもしれないけど、あまり複雑なものを入れない方がいい。

  • To have Angular call getHeroes(), you can implement the Angular ngOnInit lifecycle hook. Angularに、getHeroesを呼び出させるためには、Angular ngOnInit lifecycle hook をimplementsとして使うといいかもしれんとのこと。

  • implememnts は、クラスの継承に似ている.例えば以下のようなコード。

interface MyInterface{
    name:string
}

class MyClass implements MyInterface{
    name:string;//このメンバーがない場合コンパイルエラーとなる。
}

こちらのコードはQiitaの以下の記事より参考にさせていただきました。 http://qiita.com/nogson/items/86b47ee6947f505f6a7b

  • 以下のコードは例(不要)
import { OnInit } from '@angular/core';

export class AppComponent implements OnInit {
  ngOnInit(): void {
  }
}
  • 実際のコードは以下の通り
export class AppComponent implements OnInit { // ここでOnInitを継承する
  title = 'Tour of Heroes';
  heroes: Hero[];
  selectedHero: Hero;

  constructor(private heroService: HeroService){}

  getHeroes(): void {
      this.heroes = this.heroService.getHeroes();
    }

  ngOnInit(): void{ // ngOnInitを設定する
    this.getHeroes();
  }

  onSelect(hero: Hero): void {
    this.selectedHero = hero;
  }
}
  • なぜOnInitが必要なのか??
  • 流れとしては、

    • constructorで、初期化メソッドを実行(private変数として、heroServiceを設定)
    • getHeroesメソッドを定義する (中身は、this.heroes に、 this.heroService.getHeroes();を代入する)
    • ngOnInit()で、たぶん、読み込みのときに、getHeroes()メソッドを呼び出すようにする。
    • そのためには、OnInitと、ngOnInitをセットで使う必要がある。
  • constructorにgetHeroes定義すればいいじゃんと思うけど、それはイマイチらしい。

Async services and Promises

  • To coordinate the view with the response, you can use Promises, which is an asynchronous technique that changes the signature of the getHeroes() method.

The hero service makes a Promise

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

import { Hero } from './hero';
import { HEROES } from './mock-heroes';

@Injectable()
export class HeroService {
  getHeroes(): Promise<Hero[]> { //Promise<> にHero[]を代入する
    return Promise.resolve(HEROES); //Promiseで、resolveしたHEROESを返す
  }
}

Act on the Promise

  • Promiseがresolve されたときに、Promiseを実行するように implementationを変更する必要あり。
getHeroes(): void {
  this.heroService.getHeroes().then(heroes => this.heroes = heroes); //Pass the callback function as an argument to the Promise's then() method:
}

まとめ

  • サービスクラスを使って、データを他のcomponentで共有できる
  • ngOnInit , implements OnInitで、初期化メソッド(自動で呼び出すメソッド)を設定する
  • providersは、freshなinstanceを設定するように命令する
  • mock heroデータを別ファイルに設定する(serviceがそれを呼び出すようにする)
  • Promiseで、非同期通信が可能になる。