Earlier we learned how to send data from Elm to JavaScript. In this section, we’ll go the other way and send data from JavaScript to Elm. The good news is we can use a port for that too. Let’s add a new port function called receiveData
right below the update
function in src/PortExamples.elm
.
Note: Going forward we’ll refer to the port for sending data to JavaScript as an outgoing port and the port for receiving data from JavaScript as an incoming port.
The following diagram shows the difference between an outgoing and incoming port.
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.
Next we need to handle ReceivedDataFromJS
in update
.
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:
Next we need to ask 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 below update
in PortExamples.elm
.
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 receiveData
port 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.
Run the following command from the beginning-elm
directory in terminal to compile PortExamples.elm
.
You should see the following error.
The error message tells us exactly why we must use Sub Msg
as subscriptions
’s return type. Let’s change the return type back to Sub Msg
.
What about receiveData
’s return type? Can we change it to Sub Msg
so they both match? Let’s try it.
Once again run the following command from the beginning-elm
directory in terminal to compile PortExamples.elm
.
Now you should see a different error.
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 return type back to Sub msg
.
msg
is less restrictive than Msg
. That’s why Elm allows us to pass a data constructor with the type Model -> Msg
to receiveData
even if it’s expecting Model -> msg
. The only thing remaining on the Elm side is to assign the subscriptions
function to the subscriptions
field in main
.
Recompile PortExamples.elm
by running the following command from the beginning-elm
directory in terminal.
Everything should compile fine. Now we need to write some JavaScript code for sending data to Elm. Modify the callback function we passed to app.ports.sendData.subscribe
in beginning-elm/index.html
like this:
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 index.html
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 using an incoming port. The incoming port returns a subscription which is given to the Elm runtime when the app is being initialized. 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: