ElmとFirebaseの記事が絶望的に少なかったので書いておくことにしました。特に認証が絡んだ記事がなかったので良ければ参考にしてください。
今回やったこと
今回行ったことは単純です。
Google認証をする -> 認証が成功すればinput
が出現 -> Firestoreからデータを取得input
のvalue
として埋める -> input
の値が変更されたときFirestoreの値(uidごと)を書き換える
JavaScriptとのやりとり: Ports
まず前提知識としてFirebaseのようにJavaScriptの基本機能ではなくライブラリでElmからその資産を使いたい場合には、Portsという仕組みを使います。Portsの基本的な使い方や概念については公式ドキュメントを読むと良いでしょう。
ドキュメントから大事な部分を抜き出しておきます。
Elm と JavaScript は、ポートを通じて互いに一方的に送信を行うことで、通信をすることができる
実装解説
Ports
Elm -> JavaScript と送信をするPortsです。
signIn
は、ボタンを押されたときに、Google認証を促す部分です。
push
は、input
の要素が書き換えられたときに、文字列をFirestoreに渡して格納する部分です。
port signIn : () -> Cmd msg
port push : String -> Cmd msg
JavaScript -> Elm と待ち構えるPortsです。
read
は、Firestoreの格納した値(文字列)を受け取ります。
signedIn
は、Google認証が成功したときにinput
を出現させます。
port read : (String -> msg) -> Sub msg
port signedIn : (Bool -> msg) -> Sub msg
ports関数を持つmoduleは、port module
と宣言し、JavaScript -> Elm
とJavaScriptからアクションをする必要がある関数はexposing(公開)する必要があります。
port module Main exposing
(...
, read
, signedIn
, ...
)
JavaScriptから待ち受ける
subscriptions
関数から待ち構える必要があります。複数待ち構える場合は、Sub.batchにリスト形式で渡します。
type Msg
= SignIn
| Push String
| Read String
| SignedIn Bool
subscriptions : Model -> Sub Msg
subscriptions model =
Sub.batch [ signedIn SignedIn, read Read ]
updateでは受け取ってセットするだけです。
type alias Model =
{ pushText : String
, isSignedIn : Bool
}
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
...
SignedIn isSignedIn ->
( { model | isSignedIn = isSignedIn }, Cmd.none )
Read text ->
( { model | pushText = text }, Cmd.none )
...
-- Modelはinput要素の出し分けやinputの値に使われます。
view : Model -> Html Msg
view { pushText, isSignedIn } =
div [ class "container" ]
[ button [ onClick SignIn ] [ text "Google サインイン" ]
, if isSignedIn then
input [ type_ "input", value pushText, onInput Push ] []
else
text ""
]
ElmからJavaScriptにメッセージを送る
先程宣言したports関数を呼び出すだけです。値を必ず渡さないといけないため、signIn
は空のタプル(Unit)を渡しています。
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
...
SignIn ->
( model, signIn () )
Push text ->
( { model | pushText = text }, push text )
...
ElmとFirebaseの初期設定
こちらがFirebaseとElmの初期設定になります。app.
と書かれていたらElmに関する呼び出し。firebase.
と書かれていたらFirebaseに関わる呼び出し(認証等)。DB.
と書かれていたらFirestoreに関する呼び出しであることを心に留めておいてください。
const app = Elm.Main.init();
const config = {
/* firebase config */
};
firebase.initializeApp(config);
const provider = new firebase.auth.GoogleAuthProvider();
const DB = firebase.firestore();
Elmから待ち構える
Elmから値を受け取りましょう。とても簡単です。app.ports.[定義したports関数].subscribe([Elmから投げられた値] => {});
という形になっているだけです。
signIn
は、Google認証をします。
push
は、Firestoreに値を入れ込みます。
// ログイン監視
app.ports.signIn.subscribe(_ => {
firebase.auth().signInWithPopup(provider).then((_) => {}).catch((error) => {});
});
app.ports.push.subscribe(text => {
const user = firebase.auth().currentUser;
DB.collection('foo').doc(user.uid).set({input: text});
});
JavaScriptからElmにメッセージを送る
またまた簡単です。app.ports.[portsで定義した関数].send([Elmに渡したい値])
となります。説明不要ですね。どちらかと言うとFirebaseの知識が肝です。onAuthStateChangedでサインインが成功(userがある)したときに、FirestoreのonSnapshot
のコールバックでsendするのがポイントです。
// ログイン監視
firebase.auth().onAuthStateChanged((user) => {
if (user) {
app.ports.signedIn.send(true);
// ログイン後にDatabase監視
DB.collection('foo').doc(user.uid).onSnapshot((doc) => {
const data = doc.data();
app.ports.read.send(data.input);
});
}
});
ソースコード全体
こちらにソースコードがあります。通してみてイメージを膨らませてください。
まとめ
ElmとJavaScriptが通信するにはメッセージを一方的に送信しあうことがポイントです。そのイメージが掴めればFirebaseでも他のJavaScriptライブラリでも要領は同じです。Elm側は型安全や記述のシンプルさを活かしつつ、JavaScriptにしか出来ない仕事をドンドン押し付けてリッチなアプリケーションを作ってみましょう!