In chapter 7, we built a single-page app for creating, reading, updating, and deleting posts from an HTTP server. What happens to the app when the server is unreachable? Let’s find out. Run the following commands from the
beginning-elm directory in separate terminal windows to launch the app.
Now go to
http://localhost:8000 in a browser and you should see two posts.
json-server by pressing
Ctrl + c and reload the page at
http://localhost:8000. You should now see an error.
It would be great if we could display posts from the previous fetch as soon as our app loads even if the server is unreachable. As it turns out, all client-side web apps can in fact store data locally in a browser through the use of a technology called Web Storage which provides two different ways for saving data:
Session Storage - Data stored in session storage is available until the browser is closed.
Local Storage - Data stored in local storage is available even if the browser is closed and reopened.
Creating an Outgoing Port
Ports.elm in the
beginning-elm/post-app directory and add the code below to it.
Our plan is to transform all
Post records in our app to a JSON string and then store that string in local storage. That’s why the
storePosts port takes a string as an input instead of a list of posts like this:
In the Model View Update - Part 1 section, we learned that the state of an app is typically represented by a model. So why are we storing only posts and not everything inside our main and page models? That’s because we just need to store enough data to bring the app back to a usable state. In our case, all we need is a list of posts.
Next we’ll write a function for transforming posts to a JSON string. Add the following code to the bottom of
savePosts not only transforms posts into JSON, but also calls the
storePosts port function. So to send posts through that port all we have to do is call
Encode.encode function converts a
Value into an actual JSON string. Here’s what its type signature looks like:
All encoders we’ve used so far produce a
Value. They don’t actually create a JSON string. That’s the job for
We didn’t have to use
encode when editing and creating a post in chapter 7 because the
Http.post functions converted an encoded
Value to a JSON string for us behind the scenes. Here we have to do that conversion ourselves because ports don’t do it. Let’s expose
savePosts and import the
Ports module in
Where in our app would be a good place to initiate the process of saving posts in local storage? Since we always end up fetching all posts from the
ListPosts page after creating a new post or editing an existing one, the
PostsReceived response -> branch inside
update function is a good place to call the
savePosts function from.
Unfortunately that’s not going to work because
response is of type
WebData (List Post). We need to pull posts out of
WebData. Luckily, the
RemoteData module provides a function called
withDefault that does exactly what we need here. Here’s what its type signature looks like:
PostsReceived response -> branch in
update to the following.
Although this implementation works, it introduces a subtle bug: An empty list is saved in local storage even if we failed to fetch posts from the server. We don’t want that.
savePosts shouldn’t be called at all if we don’t have actual posts. Let’s use the following implementation instead.
savePosts when importing the
Post module in
Storing Posts in Local Storage
That’s all we need to do on the Elm side. Check the
beginning-elm/index.html with the following.
For comparison, here is the
Did you notice the difference? We had to embed our Elm app inside a
div in the latter case. We don’t have to do that in a single-page app because it takes over the entire HTML document. Simply loading
elm.js is enough.
Next we need to put the compiled code in
elm-live by pressing
Ctrl + c and restart it by running the following command from the
beginning-elm directory in terminal.
In the previous sections of this chapter we used
elm make to produce
elm.js like this:
Why can’t we use
elm make here too? That’s because
post-app is a single-page app and we need the
pushstate feature from
elm-live to be able to navigate between pages. That being said,
elm-live actually calls
elm make behind the scenes to compile our code.
It’s important to note that
elm-live requires us to put all
elm make specific flags after the
-- separator. If we don’t use the
elm make will automatically create a file called
index.html in the directory from where it’s run and put the compiled code in that file instead. We don’t want that.
Listening to the storePosts Port
Now it’s time to subscribe to the
storePosts port by adding the following code to the bottom of the
<script> tag in
json-server are running. Then go to
http://localhost:8000 and open the browser console. You should see the posts in raw JSON string format.
Now update the callback function in
index.html to actually save the posts in local storage.
We’re using the
setItem function in
localStorage to save the posts.
setItem takes a key value pair.
post-app-save is the key and the raw JSON string produced by
JSON.stringify is the value.
If you are using Chrome, you can check whether or not the data was indeed saved in local storage by opening the developer tools and going to the Application tab as shown in the figure below.