読者です 読者をやめる 読者になる 読者になる

Djangoroidの奮闘記

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

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

HTTP

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

javascriptの基礎

http://qiita.com/ukiuni@github/items/5f3d8620187905aea3d4

  • Convert the service and components to use Angular’s HTTP service.

  • Get the hero data from a server.

  • Let users add, edit, and delete hero names. CRUD
  • Save the changes to the server.

Providing HTTP Services

  • HttpModuleは、core moduleではない。あくまでもoptionの1つ。
  • app.module.tsに、HttpModuleをimportする
import { NgModule }      from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule }   from '@angular/forms';

import { HttpModule }    from '@angular/http'; //ここをimportする

import { AppRoutingModule } from './app-routing.module';
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';
@NgModule({
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule, // ここにもimportする
    AppRoutingModule
  ],
  declarations: [
    AppComponent,
    DashboardComponent,
    HeroDetailComponent,
    HeroesComponent,
  ],
  providers: [ HeroService ],
  bootstrap: [ AppComponent ]
})
export class AppModule { }

Simulate the web API

  • We recommend registering app-wide services in the root AppModule providers.app-wide servicesは、root Appmoduleのprovidersに登録しておくのがおすすめ。HeroServiceのことかな。どこからでもアクセスできるからかな。
  • とりあえず、in-memory web APIでテストする。
  • 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 { 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'; //ここを追記
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';
@NgModule({
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule,
    InMemoryWebApiModule.forRoot(InMemoryDataService), // InMemoryDataServiceからデータを持ってくるとかそれぐらいの意味なのかな?forRootってのは、dataを持ってくるときにつかうのか。
    AppRoutingModule
  ],
  declarations: [
    AppComponent,
    DashboardComponent,
    HeroDetailComponent,
    HeroesComponent,
  ],
  providers: [ HeroService ],
  bootstrap: [ AppComponent ]
})
export class AppModule { }
  • forRoot method は、the name of the method is forRoot, Angular will know how to use it: it will create the module context and the services declared in providers.ということらしい。
  • とりあえず、forRootは、Serviceをimportと同じような意味と考えておこう。
  • module内に、Serviceをimportする。

  • つぎにin-memory-data.service.tsを作成する。

import { InMemoryDbService } from 'angular-in-memory-web-api';
export class InMemoryDataService implements InMemoryDbService { // implementsで、InMemoryDbServiceを継承する。
  createDb() { //createDbはたぶん、InMermoryDbServiceのメソッド南じゃないかな。
    let heroes = [ //letは、変更可能な変数
      {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};
  }
}

Heroes and HTTP

  • Now convert getHeroes() to use HTTP. hero.service.tsをupdateする。
private heroesUrl = 'api/heroes';  // URL to web api
constructor(private http: Http) { } // Httpのclassをimportしておく必要あり、httpの型を定義する。
getHeroes(): Promise<Hero[]> { //getHeroesをPromiseで1回呼び出す、getHeroesの型は、Hero[]リストの型
  return this.http.get(this.heroesUrl) // とにかく、httpで、heroesUrlにアクセスする
             .toPromise() // Promise objectに変換するという意味か!
             .then(response => response.json().data as Hero[]) // =>はarrow関数といういうのか。arrow関数は無記名関数の省略形。
             //  function(response) { return response.json().data as Hero[] }
             .catch(this.handleError);
             // try exceptのようなものと考えていいか?handleErrorは、↓で定義する。
}
private handleError(error: any): Promise<any> { // errorのclassは、anyでOK。
  console.error('An error occurred', error); // for demo purposes only // consolenerrorを表示する、error内容も表示する。
  return Promise.reject(error.message || error); //静的なPromise.reject 関数はリジェクトされたPromiseを返します。デバッグとエラーをキャッチするために、 reasonをinstanceof Errorするのは役に立ちます。とのことなのえ、rejectされた、error.messageとerrorを返すということでOKか。
}
  • Promise.reject は、関数はリジェクトされたPromiseを返します。デバッグとエラーをキャッチするために、 reasonをinstanceof Errorするのは役に立ちます。とのこと。

Get hero by id

  • 現在の、getheroでは、すべてのheroesを取ってきて、そこからfilterして、idがmatchするものを選択していたが、これは無駄が多い。そのため、idで検索してmatchするdataのみ表示するように変更する。
getHero(id: number): Promise<Hero> { // 引数であるidをnumber型で設定する。Promise<Hero>で、getHeroの型をHero classに設定するかつPromiseを有効にする
  const url = `${this.heroesUrl}/${id}`; // url は、constで、private変数のheroesUrl/idに設定する。
  return this.http.get(url) // httpのget methodで上記のURLにアクセスする
    .toPromise() // Promiseオブジェクトに変換する
    .then(response => response.json().data as Hero) // function(response){ return this.response.json().dataを1行で表した=>(arrow関数)
    .catch(this.handleError); //try, exceptのexceptの部分。handleErrorメソッドを呼び出す。
}
  • ${}は、template literalの中に、変数を入れるときの、typescriptの文法

Updating hero details

  • hero-detail.component.html にsave ボタンを追加する。
<button (click)="save()">Save</button>
  • hero-detail.component.ts
save(): void { // void関数
  this.heroService.update(this.hero) //heroServiceのupdateメソッドを実行する
    .then(() => this.goBack()); // 成功した場合、goBackメソッドを実行する。 function(){ return this.goBack()} になるのか?
}
  • arrow関数がイマイチわかってないな、、、() => this.goBack()は、return this.goBack()と同じ意味になるのか?それとも、function(){ return this.goBack() }になるのか?

Add a hero service update() method

  • さっきのsave()メソッドで使った、update()メソッドをhero.service.tsファイル内で定義する。
private headers = new Headers({'Content-Type': 'application/json'}); // headersをprivate変数として定義する。newで新しいHeadersオブジェクトを作成する
update(hero: Hero): Promise<Hero> { //updateメソッドの引数は、hero(型はHero)、classは、Hero
  const url = `${this.heroesUrl}/${hero.id}`; // ${}は、template literal内で、変数を使うときに利用する。this.heroesUrl/hero.id
  return this.http
    .put(url, JSON.stringify(hero), {headers: this.headers}) //httpのputメソッドで、urlにアクセスする。
    // headers : 開いているのページのheadersを設定する(python風にいうと、self.request.headersとかになるのかな)
    .toPromise()
    .then(() => hero)
    .catch(this.handleError);
}
  • Http classのメソッドの一覧は以下にある模様。

https://angular.io/docs/ts/latest/api/http/index/Http-class.html

  • put(url: string, body: any, options?: RequestOptionsArgs) : Observable<Response> とのことなので、put メソッドで、指定したurlに、bodyの内容を、headersをjsonで指定して、送付するということか〜〜!!

  • でもなんで、headersの情報が必要なんだ。たぶん、The body content type (application/json) is identified in the request header.ということなので、content typeを明確にするために、ということなのかな。

Add the ability to add heroes

<div>
  <label>Hero name:</label> <input #heroName />
  <button (click)="add(heroName.value); heroName.value=''">
    Add
  </button>
</div>
  • heroes.components.ts を作成する。
add(name: string): void { // addの引数であるname は、stringで型を定義する。関数の返り値は、voidにしておく。
  name = name.trim(); // trimメソッドは両端から空白を取り除いた文字列を返します。 元の文字列自身は変更しません。とのこと。
  if (!name) { return; } // nameの値ががない場合は、そこで終了するという意味なのかな?
  this.heroService.create(name) // !name出ない場合は、ここから実行する。create メソッドを実行する
    .then(hero => { // 成功した場合は、function (hero) {return this.heroes.push(this.hero), this.selectedHero = null} を実行するというのと同じ意味?
      this.heroes.push(hero); // pushは、listに値を追加するという意味らしい
      this.selectedHero = null;
    });
}
  • hero.service.ts にcreateメソッドを作成する。
create(name: string): Promise<Hero> { // create メソッドの引数は、nameで、引数は、string 返り値のclass定義は、Promiseの変数型Hero
  return this.http // http メソッドのpostで送信する。
    .post(this.heroesUrl, JSON.stringify({name: name}), {headers: this.headers}) // headersで,送信する型を定義する。
    .toPromise() //Promise型に変換する
    .then(res => res.json().data as Hero) // postが成功したら、function(res){return this.res.json().data as Hero}
    .catch(this.handleError); // errorが出た場合の処理メソッド
}

Add the ability to delete a hero

  • heroes.component.html にdeleteボタンを追記する。
<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>
  • heroes.component.ts にdeleteメソッドを追記する。
delete(hero: Hero): void { //引数hero(変数型Hero), 返り値は、void
  this.heroService
      .delete(hero.id) //deleteメソッドを実行
      .then(() => { // 成功した場合以下の関数を実行する? function(){this.heroes = this.heroes.filter( function(h){ return  h !== hero } )} だから、filterは、h以外のheroを返り値として返すということか!
        this.heroes = this.heroes.filter(h => h !== hero);
        if (this.selectedHero === hero) { this.selectedHero = null; } //this.selectedHeroがheroの場合は、selectedHeroはnullにする。
      });
}
  • ==と、=== は、違う。==は、等価演算子で、===は、厳密等価演算子
  • this.heroes = this.heroes.filter(h => h !== hero); って何気に難しいな。結論的には、h 以外のheroをthis.heroesに返すという機能。this.heroes = this.heroes.filter(function(h){return(h !== hero)})をアロー関数で表したもの。
  • filter機能は、以下のサイトがわかりやすかった! http://qiita.com/itagakishintaro/items/29e301f3125760b81302#filter
var filtered = [12, 5, 8, 130, 44].filter(function(element, index, array) {
    return (element >= 10);
});

Hero service delete() method

  • hero.service.ts にdeleteメソッドを定義する。
delete(id: number): Promise<void> { // 引数は、id(変数型はnumber), 返り値は、Promise<void>
  const url = `${this.heroesUrl}/${id}`; // template literalの${}
  return this.http.delete(url, {headers: this.headers}) //httpのdeleteで、urlにアクセス
    .toPromise() // Promise型に変換
    .then(() => null) // 成功したら、null を返す function(){return null}
    .catch(this.handleError);
}

Observables

  • Each Http service method returns an Observable of HTTP Response objects. Httpservice method は、HTTP Response objects である、Observableを返り値として返す。
  • The HeroService converts that Observable into a Promise and returns the promise to the caller. This section shows you how, when, and why to return the Observable directly.HeroServiceは、Observable を Promiseに変換して、関数に、the promiseを返す。
  • An Observable is a stream of events that you can process with array-like operators.Observableは、eventsのstreamで、promiseは、1回の呼び出しが基本ぽい。
  • Converting to a Promise is often a good choice. You typically ask http.get() to fetch a single chunk of data. (Observable をPromiseに変換することはいい選択であることが多い。普通は、http.get()で、single chunk of dataをfetchするからね。)
  • When you receive the data, you’re done. The calling component can easily consume a single result in the form of a Promise.(dataを受け取ったとき、処理完了。呼び出したcomponentは、Promiseの形式に従って、速やかにsingle resultを消化できる。)
  • But requests aren’t always done only once. You may start one request, cancel it, and make a different request before the server has responded to the first request.(でも一回で終わらないときもある。first requestが帰ってくる前に別のrequestを始めたり、cancelしたりするかもしれない。)
  • A request-cancel-new-request sequence is difficult to implement with Promises, but easy with Observables.(requestをcancelして、新しいrequestを始めるといったsequence は、Promisesでは実装が難しいけど、Observablesなら簡単に実装できる。)

Add the ability to search by name

  • hero-search.service.ts をObservableで処理するように変更する。
import { Injectable } from '@angular/core';
import { Http }       from '@angular/http';
import { Observable }     from 'rxjs/Observable'; // Observableをimport
import 'rxjs/add/operator/map'; // mapをimport
import { Hero }           from './hero';
@Injectable()
export class HeroSearchService {
  constructor(private http: Http) {} // private変数の httpの変数型Httpに設定する。constructorは、classから、オブジェクトを作成したときに、自動的に実行されるメソッド。
  search(term: string): Observable<Hero[]> { //term の変数型string に設定。返り値は、Observableの変数型Hero[]で設定
    return this.http
               .get(`app/heroes/?name=${term}`) // template literal 内に${term}で、検索ワードを代入したurlを設定する。
               .map(response => response.json().data as Hero[]);
               // map(function(response){return response.json().data as Hero[]})
               // mapの意味?
  }
}
  • you return the Observable from the the http.get(), after chaining it to another RxJS operator, map(), to extract heroes from the response data. (ObservableをanotherRXJSoperatorであるmap()とchainingした後に、http.get()から受け取る。map()は、response dataからheroesを抽出するために必要な処理。)
  • RxJS operator chaining makes response processing easy and readable. (RxJS operatorのchainingは、response processを、簡単で読み込みやすくする)

HeroSearchComponent

  • src/app/hero-search.component.htmlを作成する。
<div id="search-component">
  <h4>Hero Search</h4>
  <input #searchBox id="search-box" (keyup)="search(searchBox.value)" />
  <!-- search()を実行する -->
  <div>
    <!-- async属性が指定されているので、これ以降は利用開始時に非同期通信で処理される -->
    <div *ngFor="let hero of heroes | async"
         (click)="gotoDetail(hero)" class="search-result" >
      {{hero.name}}
    </div>
  </div>
</div>
  • asyncは、利用開始とともに、スクリプトは非同期で実行されます。一方でasync属性でなければ…ユーザエージェントがページを解析する前に、スクリプトが読み込まれ、直ちに実行されます。とのこと。

http://www.aiship.jp/knowhow/archives/20559

  • But as you’ll soon see, the heroes property is now an Observable of hero arrays, rather than just a hero array. (heroes propertyは、Observable array。)
  • The *ngFor can’t do anything with an Observable until you route it through the async pipe (AsyncPipe). (async pipe を使って、Observableにrouteするまでは、ngforが実行されないようにしている。)
  • The async pipe subscribes to the Observable and produces the array of heroes to *ngFor.(async pipeは、subscribes Observableしている。heroesのarrayをproduce する)

  • src/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: 16px;
  padding: 5px;
  background-color: white;
  cursor: pointer;
}
.search-result:hover {
  color: #eee;
  background-color: #607D8B;
}
#search-box{
  width: 200px;
  height: 20px;
}
  • HeroSearchComponentを作成する。
import { Component, OnInit } from '@angular/core';
import { Router }            from '@angular/router';
import { Observable }        from 'rxjs/Observable';
import { Subject }           from 'rxjs/Subject';
// Observable class extensions
import 'rxjs/add/observable/of';
// Observable operators
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/distinctUntilChanged';
import { HeroSearchService } from './hero-search.service';
import { Hero } from './hero';
@Component({
  selector: 'hero-search',
  templateUrl: './hero-search.component.html',
  styleUrls: [ './hero-search.component.css' ],
  providers: [HeroSearchService]
})
export class HeroSearchComponent implements OnInit {
  heroes: Observable<Hero[]>; //heroesの変数型は、Observableの<Hero[]>
  private searchTerms = new Subject<string>(); // searchTermsは、Subjectのstring型。
  constructor(
    private heroSearchService: HeroSearchService, // heroSearchServiceに、HeroSearchService型を代入する。
    private router: Router) {} // router: Router で設定。
  // Push a search term into the observable stream.
  search(term: string): void { // search methodを定義する。引数のterm は、string型を設定、返り値はvoid
    this.searchTerms.next(term); //Each call to search() puts a new string into this subject's observable stream by calling next().nextメソッドによって、新しい次のobservable streamを返す。
    // console.log(this.searchTerms);を書くと、その機能を確認できる。

  }
  ngOnInit(): void {
    this.heroes = this.searchTerms //searchTermsをheroesに代入する
      .debounceTime(300)        // wait 300ms after each keystroke before considering the term(検索に300ms待機)
      .distinctUntilChanged()   // ignore if next search term is same as previous(search termが前と同じものだったら、無視する)
      .switchMap(term => term   // switch to new observable each time the term changes
        // return the http search observable(switchMapによって、Observableに変換したサーチ結果をheroesに返す)
        ? this.heroSearchService.search(term) //? の役割は、tryくらいの意味かな? // こっちは、heroSearchServiceの方のsearchメソッド, term
        // or the observable of empty heroes if there was no search term
        : Observable.of<Hero[]>([])) // search termがない場合は、空のheroesリストを持つobservableオブジェクトを返す。
      .catch(error => {
        // TODO: add real error handling
        console.log(error);
        return Observable.of<Hero[]>([]);
      });
  }
  gotoDetail(hero: Hero): void { // Detail pageへのリンク
    let link = ['/detail', hero.id];
    this.router.navigate(link);
  }
}
  • Subjectは、さっぱりわからなかった。以下のサイトを参照にさせてもらいました〜。 http://qiita.com/ralph/items/f7205c8171826cc2153b#subject%E3%81%A8%E3%81%AF

  • Subjectは、短く言うと「SubscriberとObservableの2つの機能を併せ持ったもの」です。とのこと。

  • next()メソッドは、searchTerms(Subject型)が変更になるたびに、新しいsearchTermsを表示するように取り出すためのメソッド?

Add the search component to the dashboard

  • src/app/dashboard.component.htmlに、hero-searchを追記する。
<h3>Top Heroes</h3>
<div class="grid grid-pad">
  <a *ngFor="let hero of heroes"  [routerLink]="['/detail', hero.id]"  class="col-1-4">
    <div class="module hero">
      <h4>{{hero.name}}</h4>
    </div>
  </a>
</div>
<hero-search></hero-search>
  • app.module.tsにHeroSearchComponentのimportを追記する。
declarations: [
  AppComponent,
  DashboardComponent,
  HeroDetailComponent,
  HeroesComponent,
  HeroSearchComponent
],

まとめ

  • ひとまずこれで、tour of heroes は完了、typescript, javascriptがほとんどわからなかったので、かなり苦労した(^^;)明日からは、typescriptの文法書を読んでみることにする。