For AngularJS applications that use the standard ngRoute facility for navigation between multiple pages, a common issue is that while the ng-view directive allows the main body of the page to vary between routes, the surrounding template is frustratingly static. Even something as simple as updating the main page title is not supported natively.
Many applications have worked around this by having the controller manipulate state -- either setting window.title directly, or writing data into $rootScope -- but that can often lead to presentational concerns bleeding into the controller.
This article explores some techniques for allowing the view's template to interact with the site's chrome, and thus allowing these concerns to be solved in a more appropriate layer.
Route-specific Title
When navigating between views in an AngularJS application, it's desirable for the HTML <title> element to be updated so that a user with multiple tabs open can quickly understand the context of each of them, or so that a browser bookmark will recieve a sensible name.
A trivial way to accomplish this is to simply update the title in the route's controller:
However, the precise formatting of the title, much as with other aspects of how the content is presented to the user, is rightfully the concern of the template layer rather than the controller layer.
What if we could express the title as part of the view's template?
AngularJS directives allow us to support additional elements in templates, but this one is unusual in that it ought to have no affect on the rendering of the body of the document. Instead, we simply want to make the contents of the view element available for use in the page title.
This unusual behavior can be achieved simply by removing the element from the DOM during linking. The directive remains active in spite of its element being detached, and any interpolations within it will work as expected since watchers are attached to scopes rather than to DOM elements:
Here we just reflect the text content of the view-title element into the viewTitle variable, so that we can use it within the title element in the application's main template:
Watching the element's text content is a tricky way to automatically benefit from Angular's existing interpolation of the body, but performance-concious applications will probably want to avoid traversing DOM in a watcher by manually interpolating the text in the element at compile time.
Don't Repeat Yourself
Since the "view title" is often present inside some visible element on the page, we can avoid repetition by extending the directive to also support use as an attribute on an existing element. This is a simple matter of adding A to the restrict key in the directive definition, and then only conditionally removing the affected element:
The view title can then be "borrowed" from another element in the view template:
Since the view-title directive is originally encountered by angular inside the ng-view directive, references to variables remain relative to the view scope even though the title is presented outside of it.
This technique, along with a related technique for adding metadata elements to the document's head, is implemented in my AngularJS library angularjs-viewhead, which includes a working example.
Conclusion
Enthusiasts for the alternative ui-router implementation of routing often criticize the limitations of the stock ngRoute, but with some creative use of directives and scope one can achieve some more advanced capabilities without switching away wholesale from Angular's standard routing.
The fact that directives "own" an HTML element but are managed within the scope tree allows for such elements to be moved arbitrarily around the document or removed from the document altogether while still retaining their connection to their original declaration context. This creates some powerful possibilities and I'm excited to see what other techniques can come from this capability.
All code snippets within this article are released into the public domain in the hope that they will be useful, but it is presented only as prototype code and has no warranty of any kind.