7.3

Receiving Data from JavaScript

Let’s go the other way and send some data from JavaScript to Elm. The good news is we can use a port for that too. Create a new port function called receiveData right above the update function in Ports/PortExamples.elm.

port receiveData : (Model -> msg) -> Sub msg

Going forward we’ll refer to the port for sending data to JavaScript as outgoing port and the port for receiving data from JavaScript as incoming port.

The following diagram shows the difference between the outgoing and incoming ports.

One big difference between the two ports is that the outgoing port uses a command whereas the incoming port uses a subscription. Unlike the command (returned by the outgoing port), we actually want the subscription (returned by the incoming port) to send a message to our app whenever the JavaScript code sends some data.

We’ll be storing whatever value JavaScript sends in our model. Let’s add a new data constructor called ReceivedDataFromJS to the Msg type in PortExamples.elm that takes the model as an input and returns a message.

type Msg
    = SendDataToJS
    | ReceivedDataFromJS Model

Next we need to handle ReceivedDataFromJS in update.

update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        SendDataToJS ->
            ...

        ReceivedDataFromJS data ->
            ( data, Cmd.none )

All we’re doing here is return whatever data JavaScript sends as the model. Now we need to display the model. Modify the view function in PortExamples.elm like this:

view : Model -> Html Msg
view model =
    div []
        [ button [ onClick SendDataToJS ]
            [ text "Send Data to JavaScript" ]
        , br [] []
        , br [] []
        , text ("Data received from JavaScript: " ++ model)
        ]

Next we need to tell the Elm runtime to listen to the data coming from JavaScript by giving it the subscription returned by the receiveData port function. Create a function called subscriptions right above update in PortExamples.elm.

subscriptions : Model -> Sub Msg
subscriptions _ =
    receiveData ReceivedDataFromJS

Remember, the runtime passes the model to subscriptions. Since we don’t need anything in the model to create a subscription, we’re ignoring that parameter with _.

If you pay close attention to the return type of subscriptions, you’ll notice that it uses Msg whereas the return type of the receiveData port function uses msg. Shouldn’t they both match? Not in this case. Let’s try changing the return type of subscriptions in PortExamples.elm to msg and see what happens.

subscriptions : Model -> Sub msg
subscriptions _ =
    receiveData ReceivedDataFromJS

Run the following command from the beginning-elm directory in terminal to compile PortExamples.elm.

$ elm-make Ports/PortExamples.elm --output Ports/elm.js

You should see the following error.

------------------- TYPE MISMATCH -------------------- Ports/PortExamples.elm
The definition of `subscriptions` does not match its type annotation.

33| subscriptions : Model -> Sub msg
34| subscriptions _ =
35|>    receiveData ReceivedDataFromJS

The type annotation for `subscriptions` says it always returns:

    Sub msg

But the returned value (shown above) is a:

    Sub Msg

Hint: Your type annotation uses type variable `msg` which means any type of
value can flow through. Your code is saying it CANNOT be anything though! Maybe
change your type annotation to be more specific? Maybe the code has a problem?
More at:
<https://github.com/elm-lang/elm-compiler/blob/0.18.0/hints/type-annotations.md>

Detected errors in 1 module.

The error message tells us exactly why we must use Sub Msg as subscriptions’s return type. What about receiveData’s return type? Can we change it to Sub Msg so they both match? Let’s try it.

port receiveData : (Model -> Msg) -> Sub Msg

Once again run the following command from the beginning-elm directory in terminal to compile PortExamples.elm.

$ elm-make Ports/PortExamples.elm --output Ports/elm.js

Now you should see a different error.

----------------- PORT ERROR ----------------------- Ports/PortExamples.elm
Port `receiveData` has an invalid type.

30| port receiveData : (Model -> Msg) -> Sub Msg
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
You are saying it should be:

    (PortExamples.Model -> PortExamples.Msg)
    -> Platform.Sub.Sub PortExamples.Msg

But you need to use the particular format described here:
<http://guide.elm-lang.org/interop/javascript.html#ports>

Detected errors in 1 module.

As it turns out, Elm expects us to use a particular format when defining a port and that format requires us to use a type variable msg instead of a concrete type such as Msg. Let’s change receiveData’s type back to msg and subscriptions’s type back to Msg.

port receiveData : (Model -> msg) -> Sub msg


subscriptions : Model -> Sub Msg
subscriptions _ =
    receiveData ReceivedDataFromJS

msg is less restrictive than Msg. That’s why Elm allows us to pass a data constructor with the type Model -> Msg to receiveData inside subscriptions even if it’s expecting the Model -> msg type.

The only thing remaining on the Elm side is to assign the subscriptions function to the subscriptions field in main. Go ahead and do that.

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

Recompile PortExamples.elm by running the following command from the beginning-elm directory in terminal.

$ elm-make Ports/PortExamples.elm --output Ports/elm.js

Everything should compile fine. Now we need to write some JavaScript code that sends data to Elm. Modify the callback function we passed to app.ports.sendData.subscribe in Ports/index.html like this:

<script>
    .
    .
    app.ports.sendData.subscribe(function(data) {
        console.log("Data from Elm: ", data);
        app.ports.receiveData.send("Hey Elm!");
    });
</script>

We can access all ports defined in our Elm app through app.ports. To send data to Elm, we need to use the send function instead of subscribe. Open the Ports/index.html file in a browser and click the Send Data to JavaScript button. You should see Hey Elm! on the page.

The following diagram illustrates the entire workflow for sending and receiving data from JavaScript.

Summary

In this section, we learned how to receive data from JavaScript into an Elm app using an incoming port which returns a subscription. We give that subscription to the Elm runtime during initialization. Once the app is fully running, the runtime will start listening for any data coming from JavaScript. When data arrives, the runtime will route it to our update function by sending a message.

Here is the entire code from PortExamples.elm for your reference:

port module PortExamples exposing (..)

import Html exposing (..)
import Html.Events exposing (..)


type alias Model =
    String


view : Model -> Html Msg
view model =
    div []
        [ button [ onClick SendDataToJS ]
            [ text "Send Data to JavaScript" ]
        , br [] []
        , br [] []
        , text ("Data received from JavaScript: " ++ model)
        ]


type Msg
    = SendDataToJS
    | ReceivedDataFromJS Model


port sendData : String -> Cmd msg


port receiveData : (Model -> msg) -> Sub msg


subscriptions : Model -> Sub Msg
subscriptions _ =
    receiveData ReceivedDataFromJS


update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        SendDataToJS ->
            ( model, sendData "Hello JavaScript!" )

        ReceivedDataFromJS data ->
            ( data, Cmd.none )


init : ( Model, Cmd Msg )
init =
    ( "", Cmd.none )


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

New chapters are coming soon!

Sign up for the Elm Programming newsletter to get notified!

* indicates required
Close