How to use Navigation library

867 views
Skip to first unread message

Simon

unread,
Nov 25, 2016, 4:32:50 AM11/25/16
to Elm Discuss

OK, I just don’t quite understand, even while I have working code!

I have an app where, as the model changes (in particular the value representing the ‘page’ of my single page app), the Url is updated with this task

Navigation.newUrl (toUrl model)

But as the Url changes, I get a new message from Navigation because of:

main =
    Navigation.programWithFlags
        (Routing.urlParser RouteTo)
        { init = initWithFlags
        , update = Routing.update
        , view = view
        , subscriptions = subscriptions
        }

When first loading the app, this message is useful and I can use it to direct to the appropriate opening page. But thereafter this message is redundant - I already made all the model changes I wanted before and that caused the Url update. Without some care I even get a loop of each Routing message causing the

I suspect I am missing something rather important, but am not sure what?

One option would be to use anchor tags to cause the switch pages, and only do model changes when I get a RouteTo message, but even then I have some url changes resulting from the clicking on an element within a page, and I don’t think that is a place for anchors. (As I use html urls, rather than # ones I also have to be careful not to let the page get reloaded)

Erik Lott

unread,
Nov 25, 2016, 10:52:20 AM11/25/16
to Elm Discuss
When you're creating an SPA in elm, you'll generally need to choose one of two navigation styles:

1. Allow the address bar to drive your model
The standard Navigation package provides this type of functionality. Your model will respond to changes in the address bar, and your views will (generally) use view links (a tags) to change the address bar. You may have a few sprinkles of manual url changing commands throughout the app for redirection, but for the most part, the majority of url changes are caused by users clicking on html links in your view.

The changes flow like this : View --> Address Bar --> Model --> View 

2. Allow your model to drive the address bar
In this case, the address bar is simply a reflection of the current state of your model. There is no need to update the address bar manually, because it will keep itself in sync with your model. Try using a routing package like Elm Route Url (http://package.elm-lang.org/packages/rgrempel/elm-route-url/latest). Take a moment and read the package readme. It clearly explains the difference between the 2 styles of routing. If you use this style of navigation, you'll need to build your application in such a way that the navigation is kept within elm and does not require the address bar. For example, rather than including a tag links in your html view to change the url directly, your a-tags or buttons will trigger Msg's that will update your model, and then the address bar will respond to the model change.

Simon

unread,
Nov 25, 2016, 11:22:01 AM11/25/16
to Elm Discuss
Erik,
that makes such a lot of sense. Thanks for spelling it out. Is there any reason to favour one over the other, otherwise I'm likely to go with 2.
Simon

Max Goldstein

unread,
Nov 25, 2016, 11:27:15 AM11/25/16
to Elm Discuss
One of the main benefits of routing/navigation is that a user can bookmark or send a URL to someone else, and the website loads up in the correct state. Option number 1 seems like it would handle this case naturally. I'm not sure how it would work for option number 2.

Erik Lott

unread,
Nov 25, 2016, 1:16:47 PM11/25/16
to Elm Discuss
One of the main benefits of routing/navigation is that a user can bookmark or send a URL to someone else, and the website loads up in the correct state. Option number 1 seems like it would handle this case naturally. I'm not sure how it would work for option number 2.

The Elm-route-url package also take into account the initial application load, and changes to the url that occur outside of elm (e.g. the browser back button is clicked). Regardless of which navigation type you choose, these concerns are not an issue - bookmarks, etc can be done with both.

Erik Lott

unread,
Nov 25, 2016, 5:29:49 PM11/25/16
to Elm Discuss
 Is there any reason to favour one over the other, otherwise I'm likely to go with 2.

Yeah, I would say stick with option 1. Our large SPA in elm has used option 2 for the past 5 months, and we've recently swapped over to option 1. Here are some pros & cons:

Option 1:
Pros
  • Embraces the browser by creating real clickable links. I can CMD + click on a link and open it in a new tab, I can CTRL + click and open it in a new window.
  • Easier to implement in Elm (not necessarily better, just easier) compared to option 2
  • Easier to maintain to understand. 
  • Routing state does not need to be stored in the root model. This is tough to explain. Using option 1, If someone navigates to "/apples/234" in your elm app, your app would parse that url and generate a Msg like "AppleRoute 234", send the msg to update func, fetch the "apple" resource id=234 from the backend server via http, and use that data to display the "ApplePage". Done. Although we needed id "234" to load the apple data, there is no reason for us to store that id in the root model of our application - After it's been used to load the page, it can be discarded. Not so, however, for option 2. You're forced to store that ID in your root model so that you can derive the address bar url from it.  
Cons
  • Even though it is easier to maintain, read, and write (hahaha) I strongly dislike one aspect of option 1. Why should I use the browser to pass messages around my app (click an html link > the browser updates the address bar > elm root receives url change msg) when elm can pass messages internally  just fine. Arg...

Option 2
Cons
  • Lots of msg translating and indirection - There will be a healthy amount of upward messaging from child to parent (notify root that a navigation link in the child was clicked). I wouldn't recommend this to anyone just getting started with elm, but if you already have experience with elm, go for it.
  • You need to store routing state in the root model, specifically for the address bar representation.
  • Links in your html view will not be real href links, so you lose some functionality mentioned in the option1 pros.
Pro
  • All aspects of the application are driven by elm. The internal messaging is done in elm. Address bar changes are driven by Elm. You could easily swap this app to a different platform (anything that is not the browser), make a few changes to your view layer, and blammo - the app would run.

Simon

unread,
Nov 26, 2016, 1:19:09 AM11/26/16
to Elm Discuss
With option 1 how do you stop the page reloading on each click on an anchor?

Another Con of option 1 might be that it can't cover so many scenarios. Suppose that you have a form, click submit and then - on successful completion of some persistence stage - you want to redirect to another page. (Or is that possible by combining some kind of redirect command with the confirmation from the server, and again here how would you prevent page reloading?)

Wouter In t Velt

unread,
Nov 26, 2016, 7:44:20 AM11/26/16
to Elm Discuss
Thank you for the explanation Erik! With the upgrade to 0.18 and the changes in navigation, I was wondering which route (pun intended) to follow with the upgrade. Not sure I follow completely though.

In option 1, could you deal with redirect-like scenario's inside the SPA?
Like
  1. user is on the "/apples" page, showing all available apples.
  2. user types "/apples/234"  in the url-bar
  3. there happens to be no such apple
  4. I want the user to stay on the "/apples" page, and get a message like "this apple is forbidden fruit"
  5. at this point, I would want the url-bar to also say "/apples"
Can this work with option 1? Or is this only possible with option 2?

Simon

unread,
Nov 26, 2016, 10:54:53 AM11/26/16
to Elm Discuss

Here is some code to make things more concrete
https://gist.github.com/simonh1000/9368f9dbd7f93646207ec27fdf3662a2

It is based on the example from the Navigation library, but with the links changed from # to (I think they are called) HTML5 links.

I added an onClick handler to provide a preventDefault as otherwise the links 404, but with this handler the links don’t navigate instead

The aim is to get the navigation history ticking along properly without page refreshes.

I know its possible as I’ve seen it in other routers

Simon

Erik Lott

unread,
Nov 26, 2016, 5:24:53 PM11/26/16
to Elm Discuss
With option 1 how do you stop the page reloading on each click on an anchor?

I'm not sure I understand. I would think that you would want a UrlChange msg to be fired each time one of these anchors were clicked. I think I'm missing the point, so let me know.

Another Con of option 1 might be that it can't cover so many scenarios. Suppose that you have a form, click submit and then - on successful completion of some persistence stage - you want to redirect to another page. (Or is that possible by combining some kind of redirect command with the confirmation from the server, and again here how would you prevent page reloading?)

Yes, your last sentence is correct. So, if I were in your shoes, I'd do something like this:
  1. Click on submit button fires FormSubmit msg
  2. Update handles FormSubmit msg and fires some http command to your backend server.
  3. The http command completes and again your Update function handles the response msg from the http command. HERE is where I would fire a Navigation.newUrl command to change the address bar. 
  4. A UrlChange msg is produced in the root of your application (by the navigation package) and arrives at your root Update function. These is where you can change the page/adjust your root model.

Erik Lott

unread,
Nov 26, 2016, 5:57:45 PM11/26/16
to Elm Discuss
Wouter, in regards to that sequence of events, I would reduce sequence to the following question: What does my elm app do when the address bar is "/apples/234" ? If apple 234 exists, show the apple page. If apple 234 does not exist, show a "Not Found" page. 

IMHO if the user types the url "/apples/234" into the address bar, then I must consider that url to be correct, and it is the job of my elm application to respond to that url - even if only to show a NotFound page/message.

With that said, it would also be fairly trivial to, rather than displaying a NotFound page,  redirect the user to "/apples" if the user is trying to visit the url of an apple that doesn't exist i.e "/apples/234". You can easily do that using Option 1.

Erik Lott

unread,
Nov 26, 2016, 5:59:56 PM11/26/16
to Elm Discuss
The aim is to get the navigation history ticking along properly without page refreshes.

Simon, I'm just glancing at this code, but this page should perform page refreshes at all. It should only fire UrlChange events without reload the browser. Am I missing something?

Erik Lott

unread,
Nov 26, 2016, 8:00:08 PM11/26/16
to Elm Discuss
that should say :
Simon, I'm just glancing at this code, but this page shouldn't  perform page refreshes at all. It should only fire UrlChange events without reload the browser. Am I missing something?

Simon

unread,
Nov 27, 2016, 5:31:27 AM11/27/16
to Elm Discuss
Erik,
thanks so much. I now understand everything. What had confused me was the example in the Navigation library. Because that uses hash-routing , it can work with anchors and not need to create Messages and use preventdefault. I had inferred too much form that about how to work with ordinary links. It tuend out then that I only needed to make modest changes to my App to make routing helpful, rather than a massive source of extra, unused messages!

Simon

Wouter In t Velt

unread,
Nov 27, 2016, 7:58:47 AM11/27/16
to Elm Discuss
Thanks Erik, I think I got it.
In case of not found, I prefer the not found message over not found page.
With your explanations in this thread, I'll probably be able to do that.
If not, I'll be back here :)

ssciantarelli

unread,
Dec 15, 2016, 12:31:31 PM12/15/16
to Elm Discuss
Simon (especially),

I'm trying to figure out the exact same thing you were hung up on, can you (or anyone else that knows) please inform on what changes you made to get it working? I've tried doing exactly what you demonstrated in your example and run into the same issues:

I've also tried using the elm-route-url package, but it seems every time delta2url is called "previous" and "current" are always the same, and never represent what was just clicked. Here's what I tried on that front:
https://gist.github.com/ssciantarelli/0cba172048f3c9c28754ba4d0ac5a6b1

Finally, I have a StackOverflow post that ultimately explains where I'm going with this if anyone cares to comment/answer over there:

Thanks all.

Simon

unread,
Dec 15, 2016, 1:11:12 PM12/15/16
to Elm Discuss

Basically you have two ways to move around the app (not to use the word ‘navigate’):

  • buttons, which will only change the Url if you use Navigation.newUrl after their click Message returns
  • anchor links that will change the url, but to which you have add a click handler

on "click" {preventDefault = True, ...} ...

to prevent the page being totally reloaded.

Whenever the url is changed, Navigation will create a new message that you can either ignore (if you are updating your model in the original event handler), or you wait till the Navigation message hits and take action at that stage. Notionally the latter is more elegant, but I don’t think it makes much practical difference.

ssciantarelli

unread,
Dec 15, 2016, 3:49:32 PM12/15/16
to Elm Discuss
Thanks Simon, and the latter is exactly what I'm trying to do. However, with the click handler in place my update function never receives the UrlChange message. The only thing that comes through the update function is the NoOp.
Reply all
Reply to author
Forward
0 new messages