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 が用意してくれているので簡単にインストールできます。
yarn add firebase
を実行するだけでインストールできます。
package.json にfirebaseが追加されていますね。
"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 を扱うためのサービスを追加します。
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
で定義されている export
を firebase
という変数にして 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のユーザを作成するコードを書きましょう。
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);
});
}
}
各種サービスは、実際に使われるときに始めてコンストラクタが実行されるので、商品一覧ページで使う準備だけしておきましょう。
... 略
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
をコンソールに表示するようにしています。
動作確認
では、実際に動作確認してみましょう。
console
に新規作成したユーザの uid
が表示されましたね。
エラーしか表示されない場合は、すでにユーザが存在している可能性があります。 firebase.service.ts
の EMAIL
をまだ登録していないメールアドレスに変更して試してみてください。
ここまでのコード
EMAIL, PASSWORD の部分は変更して利用してください。
Firebaseでログインする
ユーザを作成しましたが、毎回ユーザを登録していても、エラーになってしまいます。
まずログインを試して、ログインに失敗したらユーザを作成するようにしておきましょう。
... 略
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
で 先程のユーザ作成を行うようにしています。
これにより、ユーザが存在すればログイン、存在しなければユーザを作成するようになりました。
動作確認
動作確認してみましょう。
ほとんど変わっていませんが、これでログインするようになっています。
ここまでのコード
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でログインし、トークンがコンソールに出るようにします。
... 略
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()
でトークンを取得するようにしています。
動作確認
実際に動かして、トークンを取得します。
ここでコンソールに表示されたトークンをコピーします。
ここまでのコード
商品一覧をAPIで取得する。
では、取得したトークンを利用してAPIアクセスをしてみます。
今回は、 /products.json
に置かれた商品一覧を取得してみます。
app.module.ts の修正
AngularでAPIリクエストを行うには、 HttpClientModule
をモジュールにインポートする必要があります。
... 略
import { HttpClientModule } from '@angular/common/http';
... 略
imports: [
BrowserModule,
AppRoutingModule,
FormsModule,
ReactiveFormsModule,
HttpClientModule, // <= 追加
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
product.service.tsの修正
APIアクセスをして、Firebaseから商品一覧を取得するようにします。
... 略
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 で、 HttpClient
を http
という変数として取得しています。
これを使って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
に対して Observable
の operator
を渡すのをパイプするというメソッドです。
配列を受け取るので、
<observable>.pipe(
map( <observable>の返却値 => 変換したい値を返す ),
map( 上のmapの返却値 => さらに変換したい値を返す )
)
のように複数の operator
をつなげていくことができます。
pipe
はただのつなぐメソッドなので、 map
という operator
が Observable
を 配列の 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 の配列に変換しています。
動作確認
では、動作確認してみましょう。
最初に少しAPIアクセスのラグがあって、その後にちゃんと商品一覧が取得できていますね。
ここまでのコード
まとめ
今回は、AngularでFirebaseを使うにあたってのセットアップと、AngularにおけるAPIアクセスを学びました。
APIアクセスについては、非常に簡単なのですが、 Observable
が少し難しく感じたかもしれません。少しずつ使っていくうちになれていくので、今はひたすら書いてみてください。
次回は、GET以外のAPIアクセスのやり方を見ていきます。
Angular入門 未経験から1ヶ月でサービス作れるようにする その11. Firebaseを使ったAPI通信2
入門記事一覧
「Angular入門 未経験から1ヶ月でサービス作れるようにする」は、記事数が多いため、まとめ記事 を作っています。
https://qiita.com/seteen/items/43908e33e08a39612a07