Angular Logo

[Angular]チュートリアルその 7 HTTP

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

InMemoryWebApiModuleInMemoryDataServiceapp.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.tsHTTPClient をインポートします。

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.tsgetHeroes メソッドを編集します。

/** 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.tshandleError メソッドを追加します。

/**
 * 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.tsgetHero メソッドを編集します。

/** 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.htmlsave ボタンを追加します。

<button (click)="save()">save</button>

hero-detail.component.tssave メソッドを追加します。

save(): void {
   this.heroService.updateHero(this.hero)
     .subscribe(() => this.goBack());
 }

HeroService に updateHero を追加する

hero.service.tsupdateHero メソッドを追加します。

/** 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.tsadd メソッドを追加します。

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.tsaddHero メソッドを追加します。

/** 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.tsdelete メソッドを追加します。

delete(hero: Hero): void {
  this.heroes = this.heroes.filter(h => h !== hero);
  this.heroService.deleteHero(hero).subscribe();
}

HeroService に deleteHero を追加する

hero.service.tsdeleteHero メソッドを追加します。

/** 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)),
    );
  }
}

完成イメージ

チュートリアルが完了するとこんな感じで動きます。
Angular 1

ソースコードについて

今までのソースコードは Github にあげてますので、詳細を確認したい方はこちらからソースコードを見てもらえればと思います。

最後に

今回のチュートリアルで HTTP 通信を利用したサーバとの通信が行えるようになりました。今回のチュートリアルで Angular のチュートリアルは全て完了となりました。

今後、発展的な実装方法などあれば随時情報を展開していきたいと思います!!

「[Angular]チュートリアルその 7 HTTP」への1件のフィードバック

  1. 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?

コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です