Out of the box, AngularJS gives you routing and the ngView directive. With these two features, you have the ability to map routes onto templates that get rendered inside of the ngView container. This works for simple, one-dimensional web sites; but, unfortunately, if you have a site that requires deep routing, AngularJS leaves you up to your own devices. In order to achieve deep routing, I've found it more useful to map routes onto action variables rather than templates; this gives you a great degree of flexibility and makes creating nested, independent views much easier.
When I think about rendering a page in an AngularJS application, I think in terms of two parallel concepts:
- Request Context
- Render Context
The Request Context is the result of route mapping. The request context contains the route parameters and the "action" onto which the route was mapped. The action variable is a dot-delimited list of values that tells the rendering engine which templates to render. In the "Adopt-a-Pet" demo, for example, the action value for a pet's medical history is this:
standard.pets.detail.medicalHistory
Each item in the action list represents a view within the page that [generally speaking] has a corresponding controller instance.
Graphically, you can think about the relationship between the Request Context and the Render Context as such:
The Render Context is the portion of the Request Context that pertains to a given, nested view. Because the Render Context is a subset of the Request Context, the render context doesn't necessarily need to react to all changes in the Request Context. In fact, the Render Context only needs to react when relevant portions of the Request Context change.
A stripped-down Controller instance would look like this:
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071 |
|
As you can see, the "render context" for the given controller involves the action path, "standard.pets.detail" and the route param, "petID". A render context can listen to any portion of the action as well as any subset of route parameters. When the request context changes, the application controller broadcasts the "requestContextChanged" event. The controller listens for this event and checks to see if the change is relevant to the current "render context":
123456 |
|
If the request context change is relevant to the given render context, the Controller may choose to take action (such as reloading any remote data relevant to the rendered view).
If you look at the code, you can see that the renderContext instance exposes the method, getNextSection(). This method returns the next value in the action chain, relative to the render context. So, for example, if the route action was:
standard.pets.detail.medicalHistory
... and the render context location was:
standard.pets.detail
... a call to getNextSection() would return, "medicalHistory." This ability to see the next action in the action path allows the relevant view to figure out which subview to render (if there are any).
A stripped-down View would then look like this:
123456789101112131415161718 |
|
As you can see, we use the ngSwitch and ngSwitchWhen directives to render the appropriate subview based on the render context.
I've only been using this approach for a few months; but so far, I've been extremely pleased with the results. This Request Context / Render Context construct allows Controllers to change only when they need to, and no more. Furthermore, I believe it helps you think about Controllers as decoupled instances that rely more on the route data and less so on the inherited data. While this can call for some redundant data collection, it greatly simplifies your rendering logic.