Before we go past building trivial solutions, we need to understand one more concept in a trivial example scenario. How do we go about making backend calls? More preciously, how to connect and work with API calls.
We will start out by looking at the final result, and then we will go about building them. We are building a straightforward application that will show the current market value of bitcoin. When you start the application, you will see the following screen.
The screen shows the initial data of 0. On pressing the ‘Get Data’ button, the application will make an API call to “https://api.cryptonator.com/api/ticker/ltc-usd” URL, and display the result as it is on the screen.
It does not show the value; instead, it just displays the whole JSON payload as it is. We do not need to worry about parse JSON and other things for our API call discussion, which we will discuss later.
(It will be interesting to come back after a couple of years and recheck the value of bitcoin 🙂 )
Let’s start building the application without an API call first to get the basic screen shown.
First Model:
type alias Model =
{ rate : String
}
initData : Model
initData =
{ rate = “0”
}
We have just one field in the model record, which we kept is as String for the sake of simplicity.
To get started, we create the update section with
type Msg
= GetData
update : Msg -> Model -> Model
update msg model =
case msg of
GetData ->
dummyData
As you can expect, when a user clicks ‘GetData,’ the GetData message is fired by HTML. That is the only event we needed for the application right now. The action event on GetData does nothing but return dummy data. It is not making an API call yet.
The view is simple and straight forward. One label to show the rates and another button that initiates the GetData message.
view : Model -> Html Msg
view model =
div []
[ h3 [] [ text (“Current rate = ” ++ model.rate) ]
, button [ onClick GetData ] [ text “Get Data” ]
]
main =
Html.beginnerProgram { model = initData, view = view, update = update }
When you run the code first time, it will show the init data with 0.
On clicking GetData, it shows the dummy data as expected.
You can find the whole base code here in the Gist.
Time to add API call. There is one concept here that will look odd, but it is easy to understand. Before we go to the API call, I want to go one more time how HTML and our code interact.
view: Model –> Html Msg
update: Msg –> Model –> Model
In the above code snippet, the view takes the model and creates Html, capable of creating Msgs (like onClick or onInput). What goes into creating the message is hidden inside the Html module, and we do need to worry about; all we need to know when some user action happens, Msg will be triggered, and the ‘update’ function will be called immediately to act on it.
Now the same way, let’s think about API call; when we make an API call, we need to wait for the call to complete, and upon completion, we need to modify the model, and change in the model will update the view. Very simple. One difference between the button click event and the API event is that when API is called, there is a possibility the call might fail. So the result could come back as either successful (Ok resultValue) and the value or comeback with Error (Err _) with an error message. So we need to handle both the scenarios in the code on completion of the API call.
Let’s recap what we discussed so far
- We need to treat API call similar to Html event calls
- We need a mechanism to fire a message on completion of an API call
- On completion, we need to test for the successful execution of API and error scenario
It seems there is a lot but not really. Let’s take baby steps and grasp the concept. To make API calls, we need to install the Http module. Once installed, let’s write the code to make the API call. To make an Http Get, we can use
Http.getString uri
this to make a call and get a string response from the URI. Simple. We could create a function that does it. But we have a problem, this returned string and we need to wait for it completely. We need to modify this somehow so that, on completion, it generates a message which we can act on when we get it. How do we go about doing it?
It is solved by Http.send function. Http.send function will return a message on completion of an HTTP request. So we can modify the previous statement to something like the following
Http.send HttpDataComplete (Http.getString url)
Http.Send is a command, which will initiate the Http.getString, and on completion, it will trigger HttpDataComplete event/msg. So if we were to write a function to do this task, it would be something like this
httpGetData: Cmd Msg
httpGetData, which does not get any input data, will generate a command capable of generating messages. Now let’s look at the implementation
httpGetData =
Http.send HttpDataComplete (Http.getString “https://api.cryptonator.com/api/ticker/ltc-usd”)
We have a function to get data. Now we need to call this function when Get Data button pressed.
Our current update function signature is
update: Msg –> Model –> Model
This reads, update function takes Msg and the current state of the model and generates a new model. But the changes we made when someone presses GetData button to generate the model and create a command that could create msg (like out view definition view : Model –> Html Msg). So our update definition now changes to
update: Msg –> Model –> (Model, Cmd Msg)
With that definition, when a user clicks on ‘GetData,’ we need to call the httpGetData function. We will do it something like the following
case msg of
GetData –>
(model, httpGetData)
if you look at the return state here, following the signature (Model, Cmd Msg). Now that we made the call, how will we handle the response from getting a data call?
When you make an API call, you can get either a success or failure. On success, we change the rate value with result value. On failure, we need to reset it to initial data. In Elm, we have something Result which helps with this situation. Whenever we perform an operation that could result in success or failure, we need to use Result. With that, when Http make HttpDataComplete message, it will come back with results. So we need to modify the signature of our message to represent this scenario.
HttpDataComplete (Result Http.Error string)
Now in our update function, when we get HttpDataComplete, we need to check for success and failure.
case msg of
HttpDataComplete (Ok data) –>
HttpDataComplete (Err _) –>
The second (Err _) parameter means when you get an error with any value, perform that function.
Now we know we need to handle both the scenario, what should be done when API calls come back successful? All we need to do is modify the rate value in the model with incoming data. Now, if you remember, our update function expects the second parameter to a command generating function. After updating the model, we do not need to initiate any commands, so all we need to pass is Cmd.none. On error, reset the data model to initial data and the success scenario; the pass is no commands. So here is the completed update function.
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
GetData ->
( model, httpGetData )
HttpDataComplete (Ok val) ->
( { model | rate = val }, Cmd.none )
HttpDataComplete (Err _) ->
( initData, Cmd.none )
We are not done yet, two more things we need to do. When we move to this program model, you might want to load data from back-end API on page load, which means we need to call that function without any program intervention. To accomplish that, we need to move to a different helper function called ‘program’ in Html.
Html.program takes a different set of parameters.
- The first parameter is the initial data. It includes the initial model and any function that needs to be executed to load data from the back end. In this example, we are not expecting to load any data from API on load. So the values (model, Cmd.none)
- Second is the view, and there are no changes to it.
- The third parameter is the update function. No change there.
- The final parameter is a subscription. For Http example, there is no subscription, so we will create a dummy subscription to satisfy the signature in a minute.
subscription : Model -> Sub Msg
subscription model =
Sub.none
You can find the final source code for this here in this Gist.