I recently ran into a situation that was tripping me up, so I thought I’d share the solution here in case it’s not common sense.
First a little background:
I needed to call out to a port when initializing a page in order to load audio. The port takes a url from the page, loads it as audio, and then sends audio metadata back to the page. What was interesting was that the port worked fine until I added a command to another page on init.
The port worked in this example scenario:
Main.elm:
type Page
= Home Home.Model
| Inner Inner.Model
| NotFound
type alias Model =
{ key : Nav.Key, page : Page }
...
subscriptions : Model -> Sub Msg
subscriptions model =
case model.page of
Inner model_ ->
Sub.map InnerMsg <| Inner.subscriptions model_
_ ->
Sub.none
Page/Home.elm:
init : ( Model, Cmd Msg )
init =
( Model Success, Cmd.none )
Page/Inner.elm:
port innerPortCmd : E.Value -> Cmd msg
port innerPortSub : (E.Value -> msg) -> Sub msg
...
init : ( Model, Cmd Msg )
init =
( Model Loading, innerPortCmd <| E.int 0 )
...
subscriptions : Model -> Sub Msg
subscriptions model =
innerPortSub <| GotInnerPort << D.decodeValue D.int
The port did not work when I added a command to a separate page on init:
Page/Home.elm:
init : ( Model, Cmd Msg )
init =
-- ( Model Success, Cmd.none )
( Model Loading, Task.perform GotTime Time.now )
Page/Inner.elm:
subscriptions : Model -> Sub Msg
subscriptions model =
-- this never got called
innerPortSub <| GotInnerPort << D.decodeValue D.int
I should have realized that I couldn’t depend on model.page
to be immediately up to date (in Main.elm subscriptions), but since I encountered the above behavior I had to scratch my head for awhile…
To fix the issue I used Process.sleep 0:
Page/Inner.elm
init : ( Model, Cmd Msg )
init =
( Model Loading, Task.perform Ready <| Process.sleep 0 )
...
type Msg
= GotInnerPort (Result D.Error Int)
| Ready ()
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
GotInnerPort result ->
case result of
Err _ ->
( model, Cmd.none )
Ok num ->
( Model (Success num), Cmd.none )
Ready _ ->
( model, innerPortCmd <| E.int 0 )
This ensures that the model in Main.elm subscriptions has the correct page when the port is called!
Here is the full working example if anyone is interested: https://github.com/simplystuart/elm-ports-spa-example
I’m also curious to know if there is a better way to do this. Thanks!