In January I published a half-finished thought-piece on the old question of how to do nested views in AngularJS. That article was originally a reply to a thread on the AngularJS mailing list. In retrospect and after some feedback I want to elaborate on some points.
What was the problem again?
I argued that people should not look to views to build their app but make use of directives instead. Views are a mechanism, provided by AngularJS through the $route
service to bind a URL to a controller instance and additional parameters as well as a Template that is injected into the DOM at a point designated by the ngView
directive.
There is also the UI-Router module from Angular-UI that provides the ability to nest views inside of each other to allow a little more elaborate structures than Angular core. In UI-Router the directive that goes into the DOM is named uiView
and “views” are called “states” and are a bit more powerful than Angular core view but largely it’s the same concept.
Original Intention
Beginners coming to AngularJS are often confused about how to structure their Apps. AngularJS and the documentation provide little guidance in this regard and someone looking for answers will quickly stumble upon ngView
and get the impression that it is the way to structure your app.
The original idea behind my post was to direct attention back to directives and to get people to use them for as many things as possible because in a lot of the documentation out there directives treated as last-resort-options for special cases when the opposite is true!
Directives are the core building block of an Angular app. Use them to insert whatever structure or behavior you need in your app. Views are merely a shortcut for very simple use-cases.
Capabilities of Views and Directives
There is no difference, everything you can do with nested directives, you can do with nested views
This is the main objection I received in response to the original post. The answer to this is both yes and no, depending on your situation. Let’s first look at the case where it’s yes:
This would be a fairly simple App without any fancy behavior beyond what is offered by AngularJS built-in directives. You can split your app into a small number of modules, likely representing CRUD behavior on a REST backend, a couple of lists, forms etc. The code required to attach your data to your scope fits neatly into the controllers for each view. You can derive the entire view state of your application from the URL and the data in your models.
This architecture breaks down as soon as your try anything more sophisticated. Imagine a couple improvements: For your lists, you want endless scrolling. To implement that you have to write a directive that generates a scrolling container, checks for the scroll position and reloads data as necessary. You might have some collapsible boxes containing filters for your list view and you want the dates in the table be displayed in relative terms.
The scroll position, the collapsed-state of the boxes are all part of your view state, but not necessarily something you want to encode in the URL. Tiny directives that update the DOM locally (keeping the relative timestamps up-to-date for example) are even something that can’t be done at all with views. For performing tasks like this you need directives.
What’s more important though, is the apps structure. Due to the nature of the DOM, your application is always a tree of components. The entirety of the data in your scopes determines which components are displayed and how they look and behave.
The URL however is not a tree, it’s a linear list, thus can only be used to store the state of the list of components from the app root to one leaf-node. You could have /appState/Astate/Bstate/Cstate
or /appState/Astate/Bstate/Dstate
but not meaningfully represent the states of both C
and D
in the URL.
(Smartass-Warning No 1: Of course you can throw in objections now that you could represent trees in a linear fashion or encode arbitrary byte strings in the URL and represent whatever you want there. But that’s far beyond what $route
/UI-Router offer.)
(Smartass-Warning No 2: You could also replace state in the URL with tuples of state (exactly what UI-Router calls “Multiple named views”) whenever you have fixed tuples of components (C
and D
in the example), but that only holds as long as the tuples are predetermined. But if you do that you could also just treat them as a single component.)
View-Containers are meaningless, separated from their semantics through the routes.
The other, secondary gripe that I have with UI-Routers nested views is that they violate another core idea of AngularJS: Your DOM is the main place to describe the structure of your app. Reading a template should give you an idea of what goes where. If you want to edit a user, put a <edit-user user="user"/>
directive into your template:
- A reader will immediately see what that directive does and what data it depends on.
- If you write the directive correctly it will be location independent, you can place it somewhere else in your app, as long as you pass in a user through the attribute it will work.
Using views litters you templates with meaningless containers, outsourcing the actual purpose of every view into the routes/states defined elsewhere. If you nest routes, the context of every view becomes implicit, it is harder to move them around and the only way to pass data into a view is through the scope.
An example for this scenario: suppose you have the user-editor <edit-user user="user"/>
, it will be trivial to edit two users next to each other: <edit-user user="user1"/><edit-user user="user2"/>
and a few lines of CSS to arrange them visually and you’re done.
But URLs make the web what it is, you do want to bind your application state to the URL!
If you rely solely on the URL to store your application state you limit the complexity of what you can store. This is not necessarily bad! Quite the contrary, the simpler your app the better. But be aware of the limitations and implications of your architecture and make decisions like these consciously.
Also, embrace directives, they’re cool.
Directives are cool.
Continue reading