13
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

複数のElmアプリで小さなデータを共有する - Local storage

Last updated at Posted at 2018-05-05

 シングルページでなく複数ページでそれぞれ別のElmアプリが動作していて、小さなデータを共有したいケースを考えましょう。例えばトップページのApp.elmでログインしてサーバからJWTトークンを取得している場合、それをページ2のApp2.elmで使いたい場合はどうすればいいでしょうか。多分JavaScript(Html5)のLocal storageを使うのが一番簡単でしょう。

 Local storageには、保存したデータに関して、特に有効期限はなく、ブラウザを閉じても喪失することはなく、永続的に利用できる利点があります。もちろんJWT tokenを保存するときは、token自体の有効期限があるので、それに応じて明示的に更新(削除)する必要があるでしょう。またLocal storageの保存データサイズが5MBと比較的大きいのももう一つの利点でしょう。

 以下、今回はJWTトークンでなく、単にuser_id文字列を渡すサンプルコードを考えます。

#1.ページ設計

"/"           -- トップページ (App.elm, index.html)
"/page2"      -- ページ2(App2.elm, index2.html)

以下がApp.elmで取得したデータを、Local storageを通して、App2.elmに渡す流れです。

        port                            port
App.elm  -->  JavaScript(Local storage)  -->   App2.elm
        userId                          userId

#2.App.elm - Local storageに書く

 App.elmでは入力フォームから入力されたuserId文字列をLocalStorageに保存します。そのためにportでJavaScriptのコードにアクセスします。

#2-1.ports設計

 (key,value)のタプルを渡して、JavaScriptにLocalStorageに保存するよう依頼します。

ports
#
-- OUTGOING PORT
port portSetLocalStorage : (String, String) -> Cmd msg
#

#2-2.App.elm全コード

 以下がApp.elmの全コードです。

App.elm
port module App exposing (..)

import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (onClick, onInput)

-- OUTGOING PORT
port portSetLocalStorage : (String, String) -> Cmd msg


init : ( Model, Cmd Msg )
init =
    (Model "1111") ! []

main : Program Never Model Msg
main =
    Html.program
        { init = init
        , view = view
        , update = update
        , subscriptions = \_ -> Sub.none
        }

type Msg
  = CacheUserId
  | ChangeUserId String

type alias Model =
    {  userId : String
    }

update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
  case msg of
    CacheUserId ->
      ( model
      , portSetLocalStorage ("userId", model.userId)
      )
    ChangeUserId userId ->
      { model | userId=userId } ! []

view : Model -> Html Msg
view model =
  div []
    [ input [ value model.userId, onInput ChangeUserId ] []
    , button [ onClick CacheUserId ] [ text "Cache userId!" ]
    , div [] [ text <| "current user id = " ++ model.userId ]
    ]

#2-3.index.html

 以下がindex.htmlです。タプル(key,value)で渡された引数は、req[]配列で受け取れます。

index.html
<!doctype html>
<html>
    <head>
    </head>
    <body>
        <a href="index2.html">index2</a>
        <div id="elm-area"></div>
        <script src="app.js"></script>
        <script>
            const app = Elm.App.embed(document.getElementById("elm-area"));
            app.ports.portSetLocalStorage.subscribe( (req) => {
                localStorage.setItem(req[0],req[1]);
            } )
        </script>
    </body>
</html>

#3.App2.elm - Local storageを読む

 App2.elmではLocal storageの値を読み取ります。

#3-1.ports設計

portGetLocalStorageにkey文字列を渡して、valueを取得してくれるように依頼します。返り値はportResLocalStorageを通して行われます。(String, String)で受け取ることを宣言しているので、JavaScript側では配列で渡しますが、タプルに自動変換されます。

ports
-- OUTGOING PORT
port portGetLocalStorage : String -> Cmd msg

-- INCOMING PORT
port portResLocalStorage : ((String, String) -> msg) -> Sub msg

#3-3.App2.elm全コード

 以下がApp2.elmの全コードです。

App2.elm
port module App2 exposing (..)

import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (onClick, onInput)


-- OUTGOING PORT
port portGetLocalStorage : String -> Cmd msg

-- INCOMING PORT
port portResLocalStorage : ((String, String) -> msg) -> Sub msg



init : ( Model, Cmd Msg )
init =
    (Model "2222") ! []

main : Program Never Model Msg
main =
    Html.program
        { init = init
        , view = view
        , update = update
        , subscriptions = subscriptions
        }

type Msg
  = GetCacheUserId
  | Receive (String, String)

type alias Model =
    {  userId : String
    }

update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
  case msg of
    GetCacheUserId ->
      ( model
      , portGetLocalStorage "userId"
      )
    Receive  (_, val) ->
      {model | userId=val} ! []

view : Model -> Html Msg
view model =
  div []
    [ button [ onClick GetCacheUserId ] [ text "Get Cache userId!" ]
    , div [] [ text <| "current user id = " ++ model.userId ]
    ]

subscriptions : Model -> Sub Msg
subscriptions model =
  portResLocalStorage Receive

#3-4.index2.html

 app2.ports.portResLocalStorage.send(res)のresは配列であることに注意してください。

index2.html
<!doctype html>
<html>
    <head>
    </head>
    <body>
        <a href="index.html">top</a>
        <div id="elm-area"></div>
        <script src="app2.js"></script>
        <script>
            const app2 = Elm.App2.embed(document.getElementById("elm-area"));
            app2.ports.portGetLocalStorage.subscribe( (key) => {
            const val = localStorage.getItem(key);
            res = [key, val];
            app2.ports.portResLocalStorage.send(res);
            } )
        </script>
    </body>
</html>

#4.プログラムの実行

 適当なディレクトリを作成します。以下のようにして必要なパッケージをインストールします。

elm-package install elm-lang/html

 コンパイルとサーバの実行は以下の通りです。

elm-make App.elm --output app.js
elm-make App2.elm --output app2.js
elm-reactor -a=www.mypress.jp -p=3030

ブラウザでindex.htmlを開いてください。以下のようになっています。「13579」と入力してボタンを押します。入力欄の数字をLocalStorageに書き込みます。

image.png

 次にindex2.htmlを開いてボタンを押してください。LocalStorageの数字を読み込み表示します。

image.png

 これでApp.elmとApp2.elmでuserIdを共有させることができました。さて、これからですね。

(補足)まさに今回のテーマの仕事を行うパッケージがいくつかありました。今回のように自分で書くよりはコード量を少なくできるようですが、パッケージを使う環境を整えるのにそれ以上の労力が必要に思えました。自分でportを使って書いた方が、トータル的に考えてよいと思われます。

13
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?