In the Pure Functions section, we learned that JavaScript functions can cause side effects. Elm functions on the other hand are pure and as a result don’t cause any side effects. As nice as it is to have pure functions that are highly reliable, to do anything useful Elm programs must deal with the outside world which is riddled with side effects.
In the Elm Runtime to the Rescue section, we learned how Elm uses commands, subscriptions, and messages to properly manage side effects originated from interacting with the outside world.
The figure above shows that the way we interact with a JavaScript library is very similar to how we interact with an external service such as an HTTP server. In both cases we tell the Elm runtime to perform an operation by sending a command. When the operation is complete, the runtime sends a message back to our app.
To further understand this interaction, let’s create a simple Elm app that sends and receives data from JavaScript code. Create a file called PortExamples.elm
inside the beginning-elm/src
directory and add the code below to it.
The code above is quite simple. All it does is display a button titled Send Data to JavaScript. When that button is clicked, update
receives the SendDataToJS
message. To send data to JavaScript, we need to create a command inside the SendDataToJS ->
branch in update
.
Up until now we’ve been relying on a specific function such as Random.generate
and Http.get
to create a command.
To create a command for sending data to JavaScript we’ll have to use a different approach that involves a port. Let’s define a function called sendData
below update
in PortExamples.elm
.
Wait a minute. That doesn’t look like a function. Where is the function body? And what is the port
keyword doing in front of the function name? The diagram below answers those questions.
There is something odd about sendData
’s return type. If you compare its definition with the ones listed above for Random.generate
and Http.get
, you’ll notice that the type variable msg
appears out of nowhere in sendData
’s definition. Usually, if a type variable is included in a return type we can trace its origin back to a parameter, but that’s not the case here. sendData
’s parameter is a simple String
with no type variable.
What’s really going on is that unlike all other commands we’ve seen so far, the command generated by a port function doesn’t send a message back to the update
function once the operation is complete. If you think about it, it actually makes sense not to send any messages back to our app. All we want to do is tell the Elm runtime to send some data to JavaScript. We’re not concerned with whether that data is indeed sent to JavaScript or not.
A command that doesn’t send any messages back to the app always has the type Cmd msg
. If we had used Cmd Msg
as the return type instead, we would be implying that the command sends a message of type Msg
which is not true.
If you look inside the Platform.Cmd
module, you’ll notice that the Cmd.none
value we’ve been using throughout this book to represent the absence of a command also uses Cmd msg
as the return type.
The difference between Cmd.none
and the sendData
port function is that the former doesn’t create any command, but the latter creates a command that doesn’t send any messages back to the app. Now that we know what sendData
is and what it does, let’s use it in the update
function to create a command.
The syntax for calling a port function is identical to that of a regular function. When update
receives the SendDataToJs
message, it calls sendData
to create a command that sends the string "Hello JavaScript"
to, you guessed it, JavaScript.
Next we need to compile the Elm code in PortExamples.elm
. Run the following command from the beginning-elm
directory in terminal.
Unfortunately, the compiler throws an error.
What exactly is a port module? It’s a module whose declaration is prefixed with the keyword port
. Let’s make PortExamples
a port module by adding that keyword to the module definition in PortExamples.elm
.
Run the following command one more time from the beginning-elm
directory in terminal.
The error should go away and the elm.js
file in beginning-elm
should be overwritten with the compiled code for PortExamples
. Now we need to write some JavaScript code to receive the string sent by Elm app on the other end. Let’s replace the contents of index.html
located inside the beginning-elm
directory with the following.
The first thing we did in index.html
is create a div
that will load the Elm app. We then included the elm.js
file which contains the compiled code. Finally, the last <script>
section is where the code for receiving the string sent by our Elm app goes. Let’s go through that code step by step.
Step 1: Get hold of the div
with id elm-code-is-loaded-here
and embed the Elm app from PortExamples.elm
into that div
.
Step 2: Listen to sendData
port using the subscribe
function. When the data arrives, print it to the browser console.
All ports defined in our Elm app can be accessed through app.ports
. We’re now ready to test. Open index.html
in a browser and go to the browser console. Now click the Send Data to JavaScript button and you should see Data from Elm: Hello JavaScript!
in the console.
The following diagram illustrates the workflow we just implemented for sending data to JavaScript.
Summary
In this section, we learned how to send data to JavaScript from an Elm app using a port. Elm treats JavaScript just like an HTTP server. That’s why we need to use a command to send data to JavaScript too. Elm doesn’t allow any code to pass through a port. All we can send is data. In the next section, we’ll learn what subscriptions are. Here is the entire code from PortExamples.elm
for your reference: