Here's how to get two Elm apps on the same page sending and receiving data via Elm's ports system.
This isn't something you're going to want to do in every situation, but it's a useful way to learn how to use ports.
I'll start with an optional video walkthrough, but feel free to scroll down for the code instead.
Here's the code.
The first Elm app is a copy of the basic counter called Buttons from the Elm Guide.
It looks like this:
This app is almost identical to the version from the Guide. The first change is here:
When you're making an Elm module that uses ports, you need to explicitly tell the compiler by declaring it with port module
.
The next change is here:
Where the update
function in the basic tutorial counter app simply returns the model — that is to say, the number being incremented or decremented — this app also includes a Cmd
, and instead of the Cmd.none
seen so often in Elm apps, the Cmd
here is countOutput updated
.
The last change in this file tells the compiler what countOutput
is:
It's a port which takes an Int
. (Ports can only take types that JSON supports, i.e., strings, integers, floats, booleans, nulls, arrays, or JS objects made up of those types.)
The last big difference between this app called WidgetA
and the Buttons
app I based it on: WidgetA
has no model in its view. Instead, the WidgetA
view just shows the UI which you use to increment or decrement the counter. The goal here is to demo sending data from one Elm app to another, so I took that part of the view out of WidgetA
and put it another app called WidgetB
.
Here's that app:
Like WidgetA
, WidgetB
is cobbled together from the Buttons
example, and a couple other great tutorial examples on elmprogramming.com
.
You have to declare port module
for this app, just like you did for the other one.
Declaring the port is almost the same as the first one too:
The difference is that countInput
is a port which returns a Sub
.
That means that the WidgetB
app needs to declare it in the subscriptions
function:
Subscriptions are where you put code which handles input from outside the system. This subscription simply fires off a ReceivedDataFromJS
message, which the update
function responds to:
The code doesn't do anything with the ReceivedDataFromJS
type, going straight to the model instead, but obviously a UI with more interactivity would include ReceivedDataFromJS
in a big case
statement covering the other messages that the UI can send.
Anyway, with both widgets set up, you need HTML for the Elm apps to live in, plus a little glue code in JavaScript so the ports can communicate with each other.
Here's what that looks like:
Most of this is Elm boilerplate. Here's the important part:
The above code tells the first Elm app to send all of its output from countOutput
to an anonymous function which directs that output into the input port countInput
for the second Elm app. This is how you get widgetA.ports.countOutput
flowing into widgetB.ports.countInput
.
(Note: the naming here is a little awkward. I'm still figuring out the right way to name a port. In the video, I used count
as the name for both ports, but on reflection, I like countOutput
and countInput
better, because they make it explicit that each app has its own unique set of ports.)
So there you have it: a simple demonstration of how to connect one Elm app to another using ports.