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
- heroes.component.html
<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の文法書を読んでみることにする。