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

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

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



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

#１．ページ設計

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

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

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


#２．App.elm - Local storageに書く

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

#２－１．ports設計

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

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

#２－２．App.elm全コード

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


```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 ]
    ]
```

#２－３．index.html

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

```html: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>
```


#３．App2.elm - Local storageを読む

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

#３－１．ports設計

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

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

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

#３－３．App2.elm全コード

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


```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
```


#３－４．index2.html

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

```html: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>
```




#４．プログラムの実行


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

```
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](https://qiita-image-store.s3.amazonaws.com/0/61915/8b5e7833-3dff-e822-6d72-c3dba743ec4a.png)

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

![image.png](https://qiita-image-store.s3.amazonaws.com/0/61915/165aab1f-ddf7-a435-ba63-12c12fc358c8.png)


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


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