How to do routing with Om?


#1

I went through the lectures of ClojureScript and Om. The material is really great, I gained confindence in Om based development pretty quicly.

I don’t know though how to do routing on the client side with Om. How is handling of Url done in Om, how is it recommended? Are there any good practices?

Thank you for your suggestions.


#2

Hello,

I can suggest you to use Secretary library: https://github.com/gf3/secretary
You can find an example on how to use Secretary with Om here: https://github.com/omcljs/om-cookbook/tree/master/recipes/routing-with-secretary

Hope it will help you

Sincerely

Bertrand


#3

Hi @fifigyuri!

I have a post about setting up client-side routing:

Secretary is the one I use in production. I’ve also heard good things about Bidi: https://github.com/juxt/bidi

I’ve never really used it, so I don’t have any code to show.

Thanks
Eric


#4

Thank you guys! I will try your hints out.

George


#5

Hi @ffigyuri,

I’m curious how your experiments with routing with Om have gone. Have you made progress?

Eric


#6

Hi Eric,

Thanks for asking. I liked Bidi a bit more over secretary, but generally I used the scheme you’ve described in your post (http://www.lispcast.com/mastering-client-side-routing-with-secretary-and-goog-history).

I was thinking about whether it would make sense to handle changes as events and pass them into a core.async channel, similarly as other events are often handled and processed. What do you think?

George


#7

Hi George,

I think that can work great! One nice thing about core.async channels is that they serialize everything. You don’t get two page-change events happening in random orders. It will at least be some order, with each thing processed one at a time.

Eric


#8

Have a look at om-next. It is still under development and only an alpha release, but it has some really interesting changes relating to state and maintaining synchronisation etc. Start at the getting started tutorial. It seems that om-next has taken some of the good ideas from some other react interfaces, like reagent and ideas from datascript and the datomic database. Looks really good and I think greatly simplifies building om components and manging state and data synchronisation.


#9

Om next has the same (intentional) oversight in not prescribing a way to route. I think bidi and secretary should work, but it’s not really an answer to the question.


#10

Yes, your correct that om-next doesn’t directly answer the question of routing. I probably shold have been clearer about that - either secretary, bidi etc can be used to address that. However, what om-next promises is a much cleaner way to handle state, which I find makes the whole issue of routing much simpler. Once we can separate state from routing, then dealing with change brought about as you route to a new url/state becomes much simpler.

One of the things I found with Reagent was that if your application was at all complex, you soon end up with a somewhat large atom representing all the states which could be reached via different routes. There seemed to be too much of a link between the applicaiton state and your routes.

Om-next seems to offer a cleaner way to link state - or perhaps more accurately, link data to components so that you have less actual state you need to manage. This tends to make routing easier -even more critical, it makes synchronisation between your client state and server state easier because you have things tied to the components rather than to a large central stae atom which can get quite complicated.

For routing, I’ve always used secretary, but have seen some interesting write-ups about bidi. This is what I really like about the clojure environment. I can take an app I’ve written which uses one library for routing and with only minimal changes, try out an alternative library. As I gain more experience, I pull in the libs I like best and before you know it, I have a ‘framework’ which is tailored to my needs/brain rather than having to tailor my nneeds/brain to someone elses framework.


#11

I do agree with your observations about the Clojure ecosystem and its upsides. If I may pick your brain a bit, could you clarify what you mean by the state being linked to routes? I have one small reagent (re-frame) project under my belt, and the mental model I had was that yes, there was a large app-state, but their relation was not directly to routes, but to whichever components were shown at specific routes, as components subscribed to parts of the app-state. The large atom was complicated, but only as much as an om-next remote would be, and each component only dealt with the parts it was interested in. I’d love to hear more about your observations/experiences re: these differences.


#12

There are a number of different models floating about for structuring web applicaitons, especially with respect to single page apps (SPAs). I think it important to differentiate a little as the underlying philosophy can affect how you use/think about the libraries being used. One example is re-frame and either reagent or om-next. For me, re-frame is much more a framework than either om-next or reagent, which I consider to be more libraries (though om-next is possibly moving more towards a framework).

Re-frame is really a type of functional reactive programming framework which uses reagent. As such, you look at state in a slightly different way than you might when using reagent or om to devleop a more traditional SPA. I’m not terribly familiar with FRP, although what I do know about it looks very interesting. One of those things on my TODO list. However, because of its slightly different approach, I would not use it as an example here.

Your quite right that the main state management functionality is tied to the component - this is the big benefit of the reagent/react model. You link your component to a bit of state in your state atom and if the values in that bit of the atom change, the system knows the component needs to be re-rendered. A very nice model.

However, once your applicaiton becomes more complex, you tend to introduce different routes to collect related functionality into functional groups and reduce the overall interface complexity. However, because your application is usually focused on a specific domain, there is usually some relationship between these routes and that relationship is frequently based around data. this could affect either the list of components which are rendered in a route or which components need to be re-rendered. However, the real challenge is in how you structure the state atom and how complex it becomes to pull out the data from that atom.

When focusinig just on components,the state atom structure is quite straight-forward. Essentially, you have a tree of data and your component will likely start consuming data at some point int he tree and work down. This works nicely with cursors and fits well with the component model.

Once you start adding routes, things quickly become more complicated. While your routes will represent different functionality pathways, they will likely have bits of data which is shared with other components. Now we have a decision. We can just duplicate the bits of data and keep a fairly clean state structure - we can still use cursors and the components in our route can just consume the state atom from a particular point. However, now we have a bigger problem - we have multiple points in our state structure which represent the same state and which can easily get out of sync - essentially, we have created an opportunity for update anomalies. We really don’t want that.

The alternative is to sacrifice the clean state structure and now have much more complex data links into our state. The model based around the cursor no longer works as we now need to retrieve data from various parts (trees/branches) of our application state. Even worse, our components are now more tightly coupled to the structure of our state atom - if we change this structure, we now need to check that the change in structure does not break other components. Life has now become more complex.