This is a module for routing single-page-apps in Elm, building on the
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:
- Mapping changes in our app's state to changes in the browser's location.
- Mapping changes in the browser's location to changes in our app's state.
Now, you can already arrange for these things to happen using
Furthermore, there are already a wealth of complementary packages,
So, what does elm-route-url do differently than the others? First, I'll address this practically, then philosophically.
Mapping changes in the app state to a possible location change
If you were using
directly, then you would make changes to the URL with ordinary commands.
So, as you write your
update function, you would possibly return a command,
Now, you can make this work, of course. However, the
update function 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
Message in 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
Increment message and a
message. 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.
Mapping location changes to messages our app can respond to
If you use the official navigation
package in Elm 0.18 directly, you react to location changes by providing
an argument to
Navigation.program which converts a
Location to a message
your app can deal with. Those messages are then fed into your
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
location2messages will also be called when your
init function is invoked,
so you will also get access to the very first
So, that is similar to how
Navigation works. The difference is that
Navigation will 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
delta2url function, 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
(there are links to the right, if you're looking at the Elm package site).
RouteUrl module is now the "primary" module. It gives you access to the
Location object, and allows you to use the path, query and/or hash, as
The main thing that elm-route-url handles is making sure that your
delta2url functions 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.Builder module 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
RouteHash module attempts to match the old API of elm-route-hash as
closely as possible. You should be able to re-use your old
location2action functions without any changes. What will need to change is
the code in your
main module that initializes your app. The
module will probably be removed in a future version of elm-route-url, so you
should migrate to using
RouteUrl at 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:
- Using the new
RouteUrlAPI with the full path.
- Using the new
RouteUrlAPI with the hash only.
- Using the old
Note that the example code makes heavy use of the
However, as noted above, you don't necessarily need to use that -- a variety
of alternative approaches are possible.