Router for single-page-apps in Elm
Essentially, elm-route-url helps you to keep the URL displayed in the browser's location bar in sync with the state of your app. As your app changes state, the displayed URL changes to match. If the user changes the URL (through a bookmark, the back/forward button, or typing in the location bar), then your app changes state to match.
So, there are two things going on here:
So, what does elm-route-url do differently than the others? First, I'll address this practically, then philosophically.
updatefunction, you would possibly return a command, using
Now, you can make this work, of course. However, the
updatefunction isn't really the perfect place to do this. Your update function looks like this:
update : Message -> Model -> (Model, Cmd Message)
But you don't really need to know the
Messagein order to compute a new URL for the location bar. After all, it doesn't matter how you got there -- all you want to ensure is that the URL reflects the final state of your model. (For instance, consider a module with an
Incrementmessage and a
Decrementmessage. The URL doesn't care which way you arrived at a particular state).
Furthermore, every state of your model really ought to correspond with some URL. That is, given some state of your model, there must be something that you'd like to have appear in the URL. Or, to put it another way, what appears in the URL really ought to be a function of your state, not the last message you received.
So, elm-route-url asks you to implement a function with a different signature:
delta2url : Model -> Model -> Maybe UrlChange
What you get is the previous model and the new model. What you're asked to produce is possibly a change to the URL. (The reason you get both the old and new model is because sometimes it helps you decide whether to create a new history entry, or just replace the old one).
There are a couple of possible advantages to this way of doing things:
Less clutter in your
You just calculate the appropriate URL, given the state of your app. elm-route-url automatically avoids creating a new history entry if the URL hasn't changed.
elm-route-url also automatically avoids an infinite loop if the change in the app's state was already the result of a URL change.
Of course, you can solve those issues with your own code. However, if you use elm-route-url, you don't have to.
If you use the official navigation package in Elm 0.18 directly, you react to location changes by providing an argument to
Navigation.programwhich converts a
Locationto a message your app can deal with. Those messages are then fed into your
updatefunction as the
On the surface, elm-route-url works in a similar manner, except that it asks you to implement a function which returns a list of messages. (This is possibly a convenience when you need multiple messages to react to the URL change, though of course you could also redesign your app to do multiple things with a single message).
location2messages : Location -> List Message
location2messageswill also be called when your
initfunction is invoked, so you will also get access to the very first
So, that is similar to how
Navigationworks. The difference is that
Navigationwill send you a message even when you programmatically change the URL. By contrast, elm-route-url only sends you messsages for external changes to the URL -- for instance, the user clicking on a link, opening a bookmark, or typing in the address bar. You won't get a message when you've made a change in the URL due to your
delta2urlfunction, since your state is already in sync with that URL -- no message is required.
You can, if you are so inclined, think about those differences in a more philosophical way. There is a thread on the Elm mailing list where Erik Lott gives an excellent summary. The question, he says, is whether the address bar should drive the model, or whether the model should drive the address bar. For more details, read the thread -- it really is a very good summary.
Another nice discussion of the philosophy behind elm-route-url is in a blog post by Amitai Burstein, under the heading URL Change is not Routing
For the detailed API, see the documentation for
RouteHash(there are links to the right, if you're looking at the Elm package site).
RouteUrlmodule is now the "primary" module. It gives you access to the whole
Locationobject, and allows you to use the path, query and/or hash, as you wish.
The main thing that elm-route-url handles is making sure that your
delta2urlfunctions are called at the appropriate moment. How you parse the
Location(and construct a
UrlChange) is pretty much up to you. Now, I have included a
RouteUrl.Buildermodule that could help with those tasks. However, you don't need to use it -- many other approaches would be possible, and there are links to helpful packages above. For my own part, I've been using evancz/url-parser recently to implement
RouteHashmodule attempts to match the old API of elm-route-hash as closely as possible. You should be able to re-use your old
location2actionfunctions without any changes. What will need to change is the code in your
mainmodule that initializes your app. The
RouteHashmodule will probably be removed in a future version of elm-route-url, so you should migrate to using
RouteUrlat an appropriate moment.
I've included example code which turns the old Elm Architecture Tutorial (upgraded to Elm 0.18) into a single-page app. I've included three variations:
RouteUrlAPI with the full path.
RouteUrlAPI with the hash only.
Note that the example code makes heavy use of the
RouteUrl.Buildermodule. However, as noted above, you don't necessarily need to use that -- a variety of alternative approaches are possible.