Super Simple Communication Between Browser Tabs for Elm
tl;dr use ports and localstorage
Why
Some reasons why you may want to communicate between browser tabs:
- Keep data from an API in sync/cached
- Login a user in all open tabs when they login in one
- Store auth tokens
- Use one tab to control the UI in another
In the rest of this post, I will show you a super simple example of how to talk between tabs in Elm using ports and localstorage.
Example
Click the buttons to change the counter value - then open this page in another tab, you will see the counter update in both places.
How it works
Every time we change our model in Elm, we send it out a port in our update function.
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
let
newModel =
updateHelper msg model
in
( newModel, saveToStorage newModel )
port saveToStorage : Int -> Cmd msg
On the JS side, this port saves the model to localstorage.
app.ports.saveToStorage.subscribe(function(m) {
// save our model to local storage
localStorage.setItem(lsKey, JSON.stringify(m));
});
Still in JS land, we have an event listener that subsribes to localstorage: if our model is updated in localstorage, then we send it back into a port in our Elm app.
window.addEventListener('storage', function (event) {
if (event.key === lsKey) {
// if the model changes, pass it into elm
app.ports.fromStorage.send(event.newValue);
}
});
In elm, we then decode the value and put it in our model.
port fromStorage : (Maybe String -> msg) -> Sub msg
subscriptions model =
fromStorage FromStorage
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
let
newModel =
updateHelper msg model
in
( newModel, saveToStorage newModel )
updateHelper : Msg -> Model -> Model
updateHelper msg model =
case msg of
Increment ->
model + 1
Decrement ->
model - 1
FromStorage ms ->
decodeLocalStorage ms
decodeLocalStorage : Maybe String -> Int
decodeLocalStorage ms =
case ms of
Nothing ->
0
Just s ->
Json.Decode.decodeString Json.Decode.int s
|> Result.withDefault 0
You can see the full source code in this gist. If you want to see a more complicated example, have a look at apostello.