Help us understand the problem. What is going on with this article?

Angular入門 未経験から1ヶ月でサービス作れるようにする その10. Firebaseを使ったAPI通信1

More than 1 year has passed since last update.

Angularでの HTTP 通信のやり方を解説していきます。

サーバ側は、Firebaseを使います。
Firebaseの Realtime Database は、使いやすいREST API を用意してくれているので、これを利用していきます。

なお、Firebaseを普通に使うだけなら、ライブラリでやったほうが良いです。
今回は、AngularのAPI通信部分の学習のためにREST API を使っていきます。

  • FirebaseライブラリのAngularプロジェクトへの追加(セットアップ)
  • FirebaseのREST APIの仕様
  • AngularにおけるHTTPのGETメソッド

前回の振り返り

前回は、Angularでの自作のバリデーションを学びました。
今回の内容とは、少し離れていますが、バリデーションはとても大切なので読んでない方はぜひ読んでおいてください。

この記事のソースコード

https://github.com/seteen/AngularGuides/tree/入門その10

この入門でFirebaseを使って目指すもの

  • ある特定のユーザでFirebaseにログインして、商品情報をAPIでサーバから取得するようにする
  • 商品情報を追加、更新、削除できるようにする

この入門では、この2つの項目の達成を目指します。
今回は、Firebaseのプロジェクトの作成方法や色々なユーザでログインしてそれぞれの情報が見れるようにする、などについては行いませんのでご留意ください。

FirebaseライブラリのAngularプロジェクトへの追加(セットアップ)

Firebaseライブラリのインストール

まずは、Firebaseをプロジェクトに追加していきます。
Firebase は npm package を Google が用意してくれているので簡単にインストールできます。

https://www.npmjs.com/package/firebase

yarn add firebase

を実行するだけでインストールできます。

package.json にfirebaseが追加されていますね。

package.json
  "dependencies": {
    "@angular/animations": "^6.0.3",
    "@angular/common": "^6.0.3",
    "@angular/compiler": "^6.0.3",
    "@angular/core": "^6.0.3",
    "@angular/forms": "^6.0.3",
    "@angular/http": "^6.0.3",
    "@angular/platform-browser": "^6.0.3",
    "@angular/platform-browser-dynamic": "^6.0.3",
    "@angular/router": "^6.0.3",
    "core-js": "^2.5.4",
    "firebase": "^5.4.0", // <- 追加されている
    "rxjs": "^6.0.0",
    "zone.js": "^0.8.26"
  },

Firebaseを司るサービスを追加する

Firebase を扱うためのサービスを追加します。

src/app/shared/services/firebase.service.ts
import { Injectable } from '@angular/core';
import * as firebase from 'firebase';

@Injectable({
  providedIn: 'root'
})
export class FirebaseService {
  constructor() {
    const config = {
      apiKey: 'AIzaSyAIvHZtzbQH_nXUJ1boxbxL14IOPuRHo9c',
      authDomain: 'angular-guide-firebase.firebaseapp.com',
      databaseURL: 'https://angular-guide-firebase.firebaseio.com',
      projectId: 'angular-guide-firebase',
      storageBucket: 'angular-guide-firebase.appspot.com',
      messagingSenderId: '582123911754',
    };
    firebase.initializeApp(config);
  }
}

解説

import 文
import * as firebase from 'firebase';

Webstormなら、 fireba とかまで打つと自動で import 文を挿入してくれるのであまり気にする必要はありませんが、少し特殊な import 文がでてきました。
これは、 firebase/index.d.ts で定義されている exportfirebase という変数にして import する、という意味になります。

constructor部分
  constructor() {
    const config = {
      apiKey: 'AIzaSyAIvHZtzbQH_nXUJ1boxbxL14IOPuRHo9c',
      authDomain: 'angular-guide-firebase.firebaseapp.com',
      databaseURL: 'https://angular-guide-firebase.firebaseio.com',
      projectId: 'angular-guide-firebase',
      storageBucket: 'angular-guide-firebase.appspot.com',
      messagingSenderId: '582123911754',
    };
    firebase.initializeApp(config);
  }

この部分は、この入門で使う firebase のプロジェクトの初期化をしています。

firebase のプロジェクトを作ったらでてくるコードをほとんどそのまま貼っています。

Firebaseのユーザを新規作成する

この入門では、新しいユーザを作成し、そのユーザの情報を使ってAPIアクセスを行います。
そのため、まず最初にFirebaseのユーザを作成するコードを書きましょう。

src/app/shared/services/firebase.service.ts
import { Injectable } from '@angular/core';
import * as firebase from 'firebase';
import UserCredential = firebase.auth.UserCredential;

@Injectable({
  providedIn: 'root'
})
export class FirebaseService {
  EMAIL = 'test@example.com'; // <= 自分のメールアドレスに変更してください
  PASSWORD = 'password' // <= 自分の設定したいパスワードに変更してください

  constructor() {
    const config = {
      apiKey: 'AIzaSyAIvHZtzbQH_nXUJ1boxbxL14IOPuRHo9c',
      authDomain: 'angular-guide-firebase.firebaseapp.com',
      databaseURL: 'https://angular-guide-firebase.firebaseio.com',
      projectId: 'angular-guide-firebase',
      storageBucket: 'angular-guide-firebase.appspot.com',
      messagingSenderId: '582123911754',
    };
    firebase.initializeApp(config);
    this.createUser(this.EMAIL, this.PASSWORD); // <= 追加
  }

  createUser(email, password): void { // <= 追加
    firebase.auth().createUserWithEmailAndPassword(email, password).then((userCredential: UserCredential) => {
      console.log(userCredential.user.uid);
    });
  }
}

各種サービスは、実際に使われるときに始めてコンストラクタが実行されるので、商品一覧ページで使う準備だけしておきましょう。

src/app/product/product-list/product-list.component.ts
... 
export class ProductListComponent implements OnInit {
  products: ProductListElement[] = null;

  constructor(
    private productService: ProductService,
    private firebaseService: FirebaseService, // <= 追加
  ) {}

... 

解説

ユーザ作成用のメソッドを定義し、それをコンストラクタで呼ぶようにしています。

createUser メソッド
  createUser(email, password): void { // <= 追加
    firebase.auth().createUserWithEmailAndPassword(email, password).then((userCredential: UserCredential) => {
      console.log(userCredential.user.uid);
    });

firebase.auth().createUserAndRetrieveDataWithEmailAndPassword(email, password) メソッドは、Firebaseでユーザを作成するメソッドです。

今回は、作成して、その uid をコンソールに表示するようにしています。

動作確認

では、実際に動作確認してみましょう。

001.gif

console に新規作成したユーザの uid が表示されましたね。
エラーしか表示されない場合は、すでにユーザが存在している可能性があります。 firebase.service.tsEMAIL をまだ登録していないメールアドレスに変更して試してみてください。

ここまでのコード

https://github.com/seteen/AngularGuides/commit/955a89229dbbcb2c9c144a98ed72dc02e1255b94

EMAIL, PASSWORD の部分は変更して利用してください。

Firebaseでログインする

ユーザを作成しましたが、毎回ユーザを登録していても、エラーになってしまいます。
まずログインを試して、ログインに失敗したらユーザを作成するようにしておきましょう。

src/app/shared/services/firebase.service.ts
... 
  constructor() {
    const config = {
      apiKey: 'AIzaSyAIvHZtzbQH_nXUJ1boxbxL14IOPuRHo9c',
      authDomain: 'angular-guide-firebase.firebaseapp.com',
      databaseURL: 'https://angular-guide-firebase.firebaseio.com',
      projectId: 'angular-guide-firebase',
      storageBucket: 'angular-guide-firebase.appspot.com',
      messagingSenderId: '582123911754',
    };
    firebase.initializeApp(config);
    this.signInOrCreateUser(this.EMAIL, this.PASSWORD); // <= 変更
  }

  signInOrCreateUser(email, password): void { // <= 変更
    firebase.auth().signInWithEmailAndPassword(email, password).then((userCredential: UserCredential) => {
      console.log(userCredential.user.uid);
    }).catch(() => {
      firebase.auth().createUserWithEmailAndPassword(email, password).then((userCredential: UserCredential) => {
        console.log(userCredential.user.uid);
      });
    });
  }
... 
}

解説

signInOrCreateUserメソッドについて

firebase.auth().signInWithEmailAndPassword(email, password) は、Firebaseでログインするメソッドです。
失敗したら、 catch で 先程のユーザ作成を行うようにしています。

これにより、ユーザが存在すればログイン、存在しなければユーザを作成するようになりました。

動作確認

動作確認してみましょう。

002.gif

ほとんど変わっていませんが、これでログインするようになっています。

ここまでのコード

https://github.com/seteen/AngularGuides/commit/108881236334e1e49719f30b79affd9bdac31774

FirebaseのREST API

FirebaseのAPIを叩く前に、どのような仕様のAPIなのかを解説しておきます。

今回利用するのは、 Firebase の Realtime Database のAPIです。

公式ドキュメント↓
https://firebase.google.com/docs/database/rest/start

(執筆時点で Cloud Firestore という後継サービスがベータで提供されているのですが、APIの仕様がわかりにくかったため、こちらを使っています)

APIの認証

firebase でログインした後のユーザデータから トークンが取得できるのですが、これを認証に利用します。

認証は、 クエリパラメータ auth に トークンを設定するだけというシンプルなものです。

curlだと下記のようになります。

curl "https://<DATABASE_NAME>.firebaseio.com/users/ada/name.json?auth=<ID_TOKEN>"

とても簡単ですね。

Realtime Database のAPIリスト

Realtime Database のデータ構造

Realtime Databaseは、単純に key: value を構造化して保存でき、JSON と同じ構造になっています。
実際、コンソール上ではデータをJSONでエクスポートすることができます。

データのリストを取得するAPI

Realtime Database のAPIは非常にシンプルで、

https://<DATABASE_NAME>.firebaseio.com/

これ以降のパスを構造化して、必要なデータを保存したり、取得したりすることができます。

データの作成

例えば、 /users/<user_id>/products にデータを保存したいと思うと、

https://<DATABASE_NAME>.firebaseio.com/users/<user_id>/products.json

に対して POST を行うことで、データを作成することができます。作成したいデータは body に JSON 形式で指定します。

データの更新

更新は、PUT or PATCH で実行可能です。
この2つの違いは、 PUT はそのものを置き換え、 PATCH は置き換えるのではなく、指定されている key のものだけを更新します。

URLは、POST や GET を行うと返却される key をパスに指定します。

https://<DATABASE_NAME>.firebaseio.com/users/<user_id>/products/-LKXOYw_-RdUKj16PE9E.json

こんな感じになります。

データの取得、削除

データの取得も同じで、URLで階層を表現して、そこに GET , DELETE を行うことでデータの取得や削除ができます。

AngularにおけるHTTPのGETメソッドの実行

ここから本題のAngularでのAPIアクセスを行っていきます。
今回は、商品一覧をAPIで取得するように変更してみましょう。

Firebaseを使ってちゃんとAPIアクセスをしようとすると少し難しいコードが出てきて混乱の元なので、
いったんコンソールからトークンをコピーしてそれを利用する方法でAPIアクセスを試していきたいと思います。

(最終的には、その難しいコードを書くところまで行きたいと思います)

トークンの取得

Firebaseでログインし、トークンがコンソールに出るようにします。

src/app/shared/services/firebase.service.ts
... 
  signInOrCreateUser(email, password): void {
    firebase.auth().signInWithEmailAndPassword(email, password).then((userCredential: UserCredential) => {
      firebase.auth().currentUser.getIdToken().then((token: string) => {
        console.log(token);
      });
    }).catch(() => {
      return firebase.auth().createUserWithEmailAndPassword(email, password).then((userCredential: UserCredential) => {
        firebase.auth().currentUser.getIdToken().then((token: string) => {
          console.log(token);
        });
      });
    });
  }
}

ログインしたら、 firebase.auth().currentUser.getIdToken() でトークンを取得するようにしています。

動作確認

実際に動かして、トークンを取得します。

003.gif

ここでコンソールに表示されたトークンをコピーします。

ここまでのコード

https://github.com/seteen/AngularGuides/commit/2105507a5c50613b7c56d221016fe6aaf2350d42

商品一覧をAPIで取得する。

では、取得したトークンを利用してAPIアクセスをしてみます。

今回は、 /products.json に置かれた商品一覧を取得してみます。

app.module.ts の修正

AngularでAPIリクエストを行うには、 HttpClientModule をモジュールにインポートする必要があります。

src/app/app.module.ts
... 
import { HttpClientModule } from '@angular/common/http';


... 
  imports: [
    BrowserModule,
    AppRoutingModule,
    FormsModule,
    ReactiveFormsModule,
    HttpClientModule, // <= 追加
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

product.service.tsの修正

APIアクセスをして、Firebaseから商品一覧を取得するようにします。

src/app/shared/services/product.service.ts
... 
import { HttpClient } from '@angular/common/http';
import { map } from 'rxjs/operators';

@Injectable({
  providedIn: 'root'
})
export class ProductService {
  // 追加↓
  TOKEN = 'eyJhbGciOiJSUzI1NiIsImtpZCI6IjMxNjAwMjk1MjI3ODA5M2RmODA3YzkxMGNjYTBmODE3YmI4ODcxY2YifQ.eyJpc3MiOiJodHRwczovL3NlY3VyZXRva2VuLmdvb2dsZS5jb20vYW5ndWxhci1ndWlkZS1maXJlYmFzZSIsImF1ZCI6ImFuZ3VsYXItZ3VpZGUtZmlyZWJhc2UiLCJhdXRoX3RpbWUiOjE1MzQ5NTkxODUsInVzZXJfaWQiOiJpTlVSbUVxakg2UjVQT2ZyMjcxbksyT0JKNUczIiwic3ViIjoiaU5VUm1FcWpINlI1UE9mcjI3MW5LMk9CSjVHMyIsImlhdCI6MTUzNDk1OTE4NSwiZXhwIjoxNTM0OTYyNzg1LCJlbWFpbCI6InRlc3RAZXhhbXBsZS5jb20iLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsImZpcmViYXNlIjp7ImlkZW50aXRpZXMiOnsiZW1haWwiOlsidGVzdEBleGFtcGxlLmNvbSJdfSwic2lnbl9pbl9wcm92aWRlciI6InBhc3N3b3JkIn19.Q47wTcpXWucwxPk2oQ9-d_KTsjVIFNk0Eo1XDlAJ6N_-XaLyQSjavoX7jU1E6RK7gsd7WP-GKSAt9vj11gS7P0KiBSo66cbjd4nmEKlm2cZjs0dG2kGDFQ2nWCUqJ-bDJDstgbRtE3o5I4irrcR2sClE2lfAtdYyDUCMgCu4ytgm5y80WkiuhSpdR4Fd4aJGFZ-tf9rv8ccGRM1U4S2XHj7vmgJLaT8AkhmDdjH0DYY0GWW6le_VcMBARsKXeBX-JlRd70RdELKo1UpkH4Uhl5Np6tGEs816vVd3y_OameSe7diC8TEYkfwvVINdloo6dHR3WytKgxGhi8F_PbE_gw';

  products = [
    new Product(1, 'Angular入門書「天地創造の章」', 3800, '神は云った。「Angularあれ」。するとAngularが出来た。'),
    new Product(2, 'Angularを覚えたら、年収も上がって、女の子にももてて、人生が変わりました!', 410, '年収300万のSEが、Angularと出会う。それは、小さな会社の社畜が始めた、最初の抵抗だった。'),
    new Product(3, '異世界転生から始めるAngular生活(1)', 680,
      'スパゲッティの沼でデスマーチ真っ最中の田中。過酷な日々からの現実逃避か彼は、異世界に放り出され、そこでAngularの入門書を拾う。現実逃避でさえ、プログラミングをするしかない彼に待ち受けるのは!?'),
  ];

  constructor(
    private http: HttpClient, // <= 追加
  ) {
  }

  list(): Observable<Product[]> { // 変更↓
    return this.http.get(`https://angular-guide-firebase.firebaseio.com/products.json`, { params: { auth: this.TOKEN } }).pipe(
      map((response: any) =>
        Object.keys(response).map((key: string) => {
          const prd = response[key];
          return new Product(prd.id, prd.name, prd.price, prd.description);
        })
      )
    );
  }

  get(id: number): Observable<Product> {
    return of(this.products[id - 1]);
  }

  update(product: Product): void {
    const index = this.products.findIndex((prd: Product) => prd.id === product.id);
    this.products[index] = product;
  }
}
解説

まず、最初の TOKEN には、先程コピーした値を入れています。

次に、 constructor で、 HttpClienthttp という変数として取得しています。
これを使ってAngularではAPIアクセスを行います。

list() の修正について

list() メソッドは、 ** APIアクセスを行い** 、 APIのレスポンスをProductの配列にする という2つの処理を行っています。

this.http.get(`https://angular-guide-firebase.firebaseio.com/products.json`, { params: { auth: this.TOKEN } })

これが、APIアクセスをしているコードです。

https://angular-guide-firebase.firebaseio.com/products.json に対して GET のリクエストを行っています。

{ params: { auth: this.TOKEN } } の部分は、 http.get() メソッドのオプションで、 params は、ここで指定したオブジェクトをクエリパラメータに変換してリクエストしてくれます。
そのため、実際には

https://angular-guide-firebase.firebaseio.com/products.json?auth=<TOKEN>

というURLにアクセスしていることになります。

http.get() は、 Observable を返します。
Observable で、返り値を変換して再度 Observable として返したいような場合には、

<observable>.pipe(
  map( <observable>の返却値 => 変換したい値を返す )
)

と行います。Javascriptで map 関数を使って 配列を別の配列に変換するときと同じことだと思えば理解が早いと思います。

実際には、 pipe という Observable のメソッドは、 Observable に対して Observableoperator を渡すのをパイプするというメソッドです。
配列を受け取るので、

<observable>.pipe(
  map( <observable>の返却値 => 変換したい値を返す ),
  map( 上のmapの返却値 => さらに変換したい値を返す )
)

のように複数の operator をつなげていくことができます。

pipe はただのつなぐメソッドなので、 map という operatorObservable を 配列の map メソッドのように変換するものとなります。

次に、レスポンスを変換する部分についてですが、実際のレスポンスは下記のJSONが返ってきています。

{
    "-LKX29TybTT3bEv5sIyy": {
        "description": "神は云った。「Angularあれ」。するとAngularが出来た。",
        "id": 1,
        "name": "Angular入門書「天地創造の章」",
        "price": 3800
    },
    "-LKX2PIcjVrwhEwtjfOE": {
        "description": "年収300万のSEが、Angularと出会う。それは、小さな会社の社畜が始めた、最初の抵抗だった。",
        "id": 2,
        "name": "Angularを覚えたら、年収も上がって、女の子にももてて、人生が変わりました!",
        "price": 410
    },
    "-LKX2VYs92mQcjaqhI6i": {
        "description": "スパゲッティの沼でデスマーチ真っ最中の田中。過酷な日々からの現実逃避か彼は、異世界に放り出され、そこでAngularの入門書を拾う。現実逃避でさえ、プログラミングをするしかない彼に待ち受けるのは!?",
        "id": 3,
        "name": "異世界転生から始めるAngular生活(1)",
        "price": 680
    }
}

これを

Object.keys(response).map((key: string) => {
  const prd = response[key];
  return new Product(prd.id, prd.name, prd.price, prd.description);
})

の部分で Product の配列に変換しています。

動作確認

では、動作確認してみましょう。

004.gif

最初に少しAPIアクセスのラグがあって、その後にちゃんと商品一覧が取得できていますね。

ここまでのコード

https://github.com/seteen/AngularGuides/commit/b2b8bad569bb47e0159a0ba9c1b6c52c1cd01207

まとめ

今回は、AngularでFirebaseを使うにあたってのセットアップと、AngularにおけるAPIアクセスを学びました。
APIアクセスについては、非常に簡単なのですが、 Observable が少し難しく感じたかもしれません。少しずつ使っていくうちになれていくので、今はひたすら書いてみてください。

次回は、GET以外のAPIアクセスのやり方を見ていきます。

Angular入門 未経験から1ヶ月でサービス作れるようにする その11. Firebaseを使ったAPI通信2

入門記事一覧

「Angular入門 未経験から1ヶ月でサービス作れるようにする」は、記事数が多いため、まとめ記事 を作っています。
https://qiita.com/seteen/items/43908e33e08a39612a07

seteen
Angularを広めたいエンジニア Ruby, Angularが好きです。
https://twitter.com/yazumoto
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away