Traversal in AngularJS using ui-router
Summary: Traversal is an alternative to routes for matching URL patterns to web application request handling. I implemented traversal in AngularJS using ui-router.
Web applications accept HTTP requests at a URL, run them through some logic, and return responses. Matching the data in the incoming request to the responsible logic is the job of the application server.
Nearly all modern application servers use the routing pattern for this job. With routes, you write a little pattern for what the URL would look like and what code should be called. If the URL matches the pattern, the matching code is called.
Most people don’t realize that there are alternatives, even alternatives that predate routing. My experience on this predates CGI, and we didn’t even have zeros and ones back then. In 1996 we published work on “object publishing” which became the system known as “traversal”.
In a nutshell, traversal assumes a URL is an object path in your application. “/departments/marketing/launchteam/doc1/delete” is then mapped to a series of nested Python dictionaries, ending with the “delete” operation. The app server takes the URL, breaks it into paths to objects, and uses the information in those objects to dispatch to your code. It is an object-oriented routing system.
AngularJS is a popular JS MVC framework. It, of course, presumes that you will write little URL patterns to specify routes to turn into views. I had an application that didn’t quite fit that mold…particular containers could hold a heterogenous set of things. Putting the object type in the URL? “/section/1/column/2/group/1/report/7/edit”? Kill me now.
I went on a little journey to implement traversal in AngularJS. Kill me now.
ui-router and State-Based Views
AngularJS unbundled their ngRoute package in the 1.2 series as people were writing interesting and more powerful alternatives. ui-router is by far the most popular and introduces some powerful, mind-altering concepts. In a nutshell, you think of your application not as URLs with requests and responses, but states in a state machines. Interacting with your web app changes the state, causing the UI to update.
Along for the ride you get a number of features that deeply change how you think about the design of your application: nested states and named views, in particular. It will take you several passes through writing your app before you achieve Zen (I am still on my way) but I can say that this is something new/fresh/clever/original.
But crap if they don’t start with declarative URL patterns as the specifier for routes and state changes. Dammit. However, they provide a completely imperative set of hooks to do whatever the damn well you please. I hijacked that and did traversal using ui-router.
Traversal with ui-router
Here’s the approach:
Fetch the graph
I had an application where I could reasonably fetch a small amount of metadata for the entire application, enough to make my tree/graph. Some applications have 500,000 objects and this wouldn’t work. I’m not convinced that I can/can’t accomplish traversal with lazy loading.
Once the data comes in, you have to walk everything and assign an _parent reference and, for efficiency sake, assign a full resource path.
The everything pattern
I have a URL pattern that matches every URL. Every time the URL changes, it pumps through my state change logic.
Get the context and view name from URL
I look at the URL and, using the traversal algorithm, convert:
parents: [departments, marketing, launchteam] parent: launchteam context: doc1 viewName: delete
Meaning, I use the URL to discern the path in the local object graph.
Find the resourcetype-viewname state
Based on that path, I get the context type. Oh, it is an ExpenseReport? That means I want the expensereport-delete state.
Transition with data
I then tell ui-router to transition to the expensereport-delete state, passing in the data we figured out (context, parents, etc.)
You have to handle default views as index, not found, errors, broadcasting an AngularJS custom event so directives can update themselves, etc.
How Well It Worked
I spent a lot of time on the Z-shaped Angular learning curve. I also didn’t realize the subtle Zen of ui-router. So it was painful getting things in place. But once in place…man, it’s a fun, object-oriented way to write Angular views.
I tried a science experiment to remove ui-router and implement traversal as my own Angular provider. The basics actually worked very quickly. It was all downhill after that. I’m not smart enough to write my own provider. Too many low-level promises to keep and optimizations to make.
Moreover, I know realize that there is another ui-router Zen out there that transcends URL. You still want some of the URL, so that bookmarks and emailing links work. But the composability of state changes is the next deep dive.
We in Pyramid/Zope have struggled to make people care about traversal. It’s like the SQL fight: they have already learned how to think square, so everything to them (including the web) is square. Same for routes. My data isn’t object oriented, so why should my URLs be?
Thus, I don’t plan to ever package this up and in fact, I don’t plan to use it. ui-router, nested states, and named views are the new direction, and AngularJS 2.0 will likely tread new territory, as does ui-router-extras and friends (future states, parallel states, etc.)