Angular
をチュートリアルを使って勉強する機会があったので、その時の内容について説明していきます。
Angular
の本家サイトのチュートリアルの「 HTTP
」の説明をしていきたいと思います。
今回のチュートリアルを行うことで、 HTTP
を利用した通信を行うことができるようになります。
本家サイトはこちらになります。
作成するプロジェクトは「 Tour of Heroes
」というアプリケーションです。
作成するアプリの概要についてはこちらから確認できます。
では、早速始めていきたいと思います!
開発環境
- macOS : Sierra 10.12.6
- node : 8.4.0
- npm : 5.3.0
- Angular : 4.3.6
HTTP サービスを有効化する
HTTP
サービスを有効にするために、 Angular
の通信メカニズムである HttpClient
を利用します。
下記の手順で HTTP
サービスを有効にします。
AppModule
を開く。HttpClientModule
をインポートします。@NgModule.imports
に追加する。
編集後の app.module.ts
は下記のようになります。
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import {FormsModule} from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';
import { AppComponent } from './app.component';
import { HeroesComponent } from './heroes/heroes.component';
import { HeroDetailComponent } from './hero-detail/hero-detail.component';
import { HeroService } from './hero.service';
import { MessagesComponent } from './messages/messages.component';
import { MessageService } from './message.service';
import { AppRoutingModule } from './/app-routing.module';
import { DashboardComponent } from './dashboard/dashboard.component';
@NgModule({
declarations: [
AppComponent,
HeroesComponent,
HeroDetailComponent,
MessagesComponent,
DashboardComponent
],
imports: [
BrowserModule,
FormsModule,
AppRoutingModule,
HttpClientModule
],
providers: [ HeroService, MessageService ],
bootstrap: [AppComponent]
})
export class AppModule { }
データサーバをシミュレートする
今回のチュートリアルでは実際にデータサーバを構築することはせずに、インメモリー型の WebAPI
モジュールを利用します。
下記コマンドを実行して、 In-memory Web API
パッケージをインストールします。
npm install angular-in-memory-web-api --save
InMemoryWebApiModule
と InMemoryDataService
を app.module.ts
にインポートします。
import { HttpClientInMemoryWebApiModule } from 'angular-in-memory-web-api';
import { InMemoryDataService } from './in-memory-data.service';
app.module.ts
の @NgModule.imports
に InMemoryDataService
の設定を記述します。
imports: [
BrowserModule,
FormsModule,
AppRoutingModule,
HttpClientModule,
// The HttpClientInMemoryWebApiModule module intercepts HTTP requests
// and returns simulated server responses.
// Remove it when a real server is ready to receive requests.
HttpClientInMemoryWebApiModule.forRoot(
InMemoryDataService, { dataEncapsulation: false }
)
],
in-memory-data.service.ts
を新規に作成します。
import { InMemoryDbService } from 'angular-in-memory-web-api';
export class InMemoryDataService implements InMemoryDbService {
createDb() {
const 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};
}
}
サービスと HTTP
hero.service.ts
に HTTPClient
をインポートします。
import { HttpClient, HttpHeaders } from '@angular/common/http';
constructor
の部分も編集します。
constructor(
private http: HttpClient,
private messageService: MessageService) { }
log
メソッドを追加します。
/** Log a HeroService message with the MessageService */
private log(message: string) {
this.messageService.add('HeroService: ' + message);
}
heroesUrl
プロパティを追加します。
private heroesUrl = 'api/heroes'; // URL to web api
HttpClient を利用してデータを取得する
hero.service.ts
の getHeroes
メソッドを編集します。
/** GET heroes from the server */
getHeroes (): Observable<Hero[]> {
return this.http.get<Hero[]>(this.heroesUrl)
}
エラーハンドリング
サーバ通信時のイレギュラーに対応するためのエラーハンドリングを行います。
hero.service.ts
に下記のインポートを追加します。
import { catchError, map, tap } from 'rxjs/operators';
getHeroes
メソッドにエラーハンドリングの処理を組み込みます。
getHeroes (): Observable<Hero[]> {
return this.http.get<Hero[]>(this.heroesUrl)
.pipe(
catchError(this.handleError('getHeroes', []))
);
}
handleError
hero.service.ts
に handleError
メソッドを追加します。
/**
* Handle Http operation that failed.
* Let the app continue.
* @param operation - name of the operation that failed
* @param result - optional value to return as the observable result
*/
private handleError<T> (operation = 'operation', result?: T) {
return (error: any): Observable<T> => {
// TODO: send the error to remote logging infrastructure
console.error(error); // log to console instead
// TODO: better job of transforming error for user consumption
this.log(`${operation} failed: ${error.message}`);
// Let the app keep running by returning an empty result.
return of(result as T);
};
}
getHeroes
メソッド実行時にログを出力できるように修正します。
/** GET heroes from the server */
getHeroes (): Observable<Hero[]> {
return this.http.get<Hero[]>(this.heroesUrl)
.pipe(
tap(heroes => this.log(`fetched heroes`)),
catchError(this.handleError('getHeroes', []))
);
}
id を利用してデータを取得する
id
を利用して対象データを取得します。
hero.service.ts
に getHero
メソッドを編集します。
/** GET hero by id. Will 404 if id not found */
getHero(id: number): Observable<Hero> {
const url = `${this.heroesUrl}/${id}`;
return this.http.get<Hero>(url).pipe(
tap(_ => this.log(`fetched hero id=${id}`)),
catchError(this.handleError<Hero>(`getHero id=${id}`))
);
}
データの保存
データの保存処理を実装していきます。
hero-detail.component.html
に save
ボタンを追加します。
<button (click)="save()">save</button>
hero-detail.component.ts
に save
メソッドを追加します。
save(): void {
this.heroService.updateHero(this.hero)
.subscribe(() => this.goBack());
}
HeroService に updateHero を追加する
hero.service.ts
に updateHero
メソッドを追加します。
/** PUT: update the hero on the server */
updateHero (hero: Hero): Observable<any> {
return this.http.put(this.heroesUrl, hero, httpOptions).pipe(
tap(_ => this.log(`updated hero id=${hero.id}`)),
catchError(this.handleError<any>('updateHero'))
);
}
httpOptions
定数を hero.service.ts
に追加します。
const httpOptions = {
headers: new HttpHeaders({ 'Content-Type': 'application/json' })
};
データの追加
データの追加処理を実装していきます。
heroes.component.html
にデータ追加用のフォームを記述します。
<div>
<label>Hero name:
<input #heroName />
</label>
<!-- (click) passes input value to add() and then clears the input -->
<button (click)="add(heroName.value); heroName.value=''">
add
</button>
</div>
heroes.component.ts
に add
メソッドを追加します。
add(name: string): void {
name = name.trim();
if (!name) { return; }
this.heroService.addHero({ name } as Hero)
.subscribe(hero => {
this.heroes.push(hero);
});
}
HeroService に addHero を追加する
hero.service.ts
に addHero
メソッドを追加します。
/** POST: add a new hero to the server */
addHero (hero: Hero): Observable<Hero> {
return this.http.post<Hero>(this.heroesUrl, hero, httpOptions).pipe(
tap((hero: Hero) => this.log(`added hero w/ id=${hero.id}`)),
catchError(this.handleError<Hero>('addHero'))
);
}
データの削除
データの削除処理を実装していきます。
heroes.component.html
に削除ボタンを配置します。
<button class="delete" title="delete hero"
(click)="delete(hero)">x</button>
heroes.component.ts
に delete
メソッドを追加します。
delete(hero: Hero): void {
this.heroes = this.heroes.filter(h => h !== hero);
this.heroService.deleteHero(hero).subscribe();
}
HeroService に deleteHero を追加する
hero.service.ts
に deleteHero
メソッドを追加します。
/** DELETE: delete the hero from the server */
deleteHero (hero: Hero | number): Observable<Hero> {
const id = typeof hero === 'number' ? hero : hero.id;
const url = `${this.heroesUrl}/${id}`;
return this.http.delete<Hero>(url, httpOptions).pipe(
tap(_ => this.log(`deleted hero id=${id}`)),
catchError(this.handleError<Hero>('deleteHero'))
);
}
名前でデータを検索する
名前でデータを検索するための searchHeroes
メソッドを hero.service.ts
に追加します。
/* GET heroes whose name contains search term */
searchHeroes(term: string): Observable<Hero[]> {
if (!term.trim()) {
// if not search term, return empty hero array.
return of([]);
}
return this.http.get<Hero[]>(`api/heroes/?name=${term}`).pipe(
tap(_ => this.log(`found heroes matching "${term}"`)),
catchError(this.handleError<Hero[]>('searchHeroes', []))
);
}
ダッシュボード画面に検索機能を追加する
dashboard.component.html
に検索フォームを追加します。
<h3>Top Heroes</h3>
<div class="grid grid-pad">
<a *ngFor="let hero of heroes" class="col-1-4"
routerLink="/detail/{{hero.id}}">
<div class="module hero">
<h4>{{hero.name}}</h4>
</div>
</a>
</div>
<app-hero-search></app-hero-search>
HeroSearchComponent を新規作成する
下記コマンドを実行して HeroSearchComponent
を追加します。
$ ng generate component hero-search
hero-search.component.html
を編集します。
<div id="search-component">
<h4>Hero Search</h4>
<input #searchBox id="search-box" (keyup)="search(searchBox.value)" />
<ul class="search-result">
<li *ngFor="let hero of heroes$ | async" >
<a routerLink="/detail/{{hero.id}}">
{{hero.name}}
</a>
</li>
</ul>
</div>
HeroSearchComponent クラスを確定する
hero-search.component.ts
を下記のように修正します。
import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { Subject } from 'rxjs/Subject';
import { of } from 'rxjs/observable/of';
import {
debounceTime, distinctUntilChanged, switchMap
} from 'rxjs/operators';
import { Hero } from '../hero';
import { HeroService } from '../hero.service';
@Component({
selector: 'app-hero-search',
templateUrl: './hero-search.component.html',
styleUrls: [ './hero-search.component.css' ]
})
export class HeroSearchComponent implements OnInit {
heroes$: Observable<Hero[]>;
private searchTerms = new Subject<string>();
constructor(private heroService: HeroService) {}
// Push a search term into the observable stream.
search(term: string): void {
this.searchTerms.next(term);
}
ngOnInit(): void {
this.heroes$ = this.searchTerms.pipe(
// wait 300ms after each keystroke before considering the term
debounceTime(300),
// ignore new term if same as previous term
distinctUntilChanged(),
// switch to new search observable each time the term changes
switchMap((term: string) => this.heroService.searchHeroes(term)),
);
}
}
完成イメージ
チュートリアルが完了するとこんな感じで動きます。
ソースコードについて
今までのソースコードは Github
にあげてますので、詳細を確認したい方はこちらからソースコードを見てもらえればと思います。
最後に
今回のチュートリアルで HTTP
通信を利用したサーバとの通信が行えるようになりました。今回のチュートリアルで Angular
のチュートリアルは全て完了となりました。
今後、発展的な実装方法などあれば随時情報を展開していきたいと思います!!
コメント
Tremendous issues here. I’m very satisfied to see your
post. Thanks so much and I’m looking forward to contact you.
Will you kindly drop me a e-mail?