Setting The Document Title Using Platform-Agnostic Methods In Angular 2 Beta 9
Yesterday, I took a quick look at trying to set the window / document title in an Angular 2 application. After I was done, however, something wasn't sitting right with me; all of the injectables in my demo specifically referenced the "browser" platform. A lot of work has been done, in Angular 2, to abstract the platform away so that it can be run a variety of different contexts. As such, I wanted to re-think setting the document title in a way that was platform agnostic.
Run this demo in my JavaScript Demos project on GitHub.
Yesterday, I made reference to three browser-specific platform services when trying to set the document title:
- ng.platform.browser.Title,
- ng.platform.browser.BrowserDomAdapter
- ng.platform.browser.DOCUMENT
Now, we want to get away from thinking about anything "browser" specific. But, we still need to be able to reference something outside of the root component (since Angular 2 can't be boostrapped on the entire HTML page). Luckily, the DOCUMENT token is also available in the common_dom export, which means its available across all DOM (Document Object Model) implementations. And, we already get a rendering engine in the Angular 2 core:
- ng.platform.common_dom.DOCUMENT
- ng.core.Renderer
Together, we can use these two services to change the document title without making any references to the "browser" itself:
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>
Setting The Document Title Using Platform-Agnostic Methods In Angular 2 Beta 9
</title>
<link rel="stylesheet" type="text/css" href="./demo.css"></link>
</head>
<body>
<h1>
Setting The Document Title Using Platform-Agnostic Methods In Angular 2 Beta 9
</h1>
<my-app>
Loading...
</my-app>
<!-- Load demo scripts. -->
<script type="text/javascript" src="../../vendor/angularjs-2-beta/9/es6-shim.min.js"></script>
<script type="text/javascript" src="../../vendor/angularjs-2-beta/9/Rx.umd.min.js"></script>
<script type="text/javascript" src="../../vendor/angularjs-2-beta/9/angular2-polyfills.min.js"></script>
<script type="text/javascript" src="../../vendor/angularjs-2-beta/9/angular2-all.umd.js"></script>
<!-- AlmondJS - minimal implementation of RequireJS. -->
<script type="text/javascript" src="../../vendor/angularjs-2-beta/9/almond.js"></script>
<script type="text/javascript">
// Defer bootstrapping until all of the components have been declared.
// --
// NOTE: Not all components have to be required here since they will be
// implicitly required by other components.
requirejs(
[ /* Using require() for better readability. */ ],
function run() {
ng.platform.browser.bootstrap( require( "App" ) );
}
);
// --------------------------------------------------------------------------- //
// --------------------------------------------------------------------------- //
// I provide the root App component.
define(
"App",
function registerApp() {
// Configure the App component definition.
ng.core
.Component({
selector: "my-app",
template:
`
<p>
<a (click)="setTitle( 'Hello' )">Set Title One</a>
—
<a (click)="setTitle( 'Goodbye' )">Set Title Two</a>
</p>
`
})
.Class({
constructor: AppController
})
;
// Notice that when we are injecting the services that we need in order
// to interact with the DOM, none of them are platform-specific (meaning,
// there's no mention of the "Browser" anywhere).
AppController.parameters = [
new ng.core.Inject( ng.core.Renderer ),
new ng.core.Inject( ng.platform.common_dom.DOCUMENT )
];
return( AppController );
// I control the App component.
function AppController( renderer, contextDocument ) {
var vm = this;
// Expose the public methods.
vm.setTitle = setTitle;
// ---
// PUBLIC METHODS.
// ---
// I set the title of the context document.
function setTitle( newTitle ) {
// Instead of assuming that we have a "browser" document, we are
// going to assume nothing. We know that we have a context
// document. And, we know that we have a rendering engine. So,
// we're just going to let the rendering engine take care of
// translating the property "title" into something the makes
// sense in the current context.
renderer.setElementProperty( contextDocument, "title", newTitle );
}
}
}
);
</script>
</body>
</html>
As you can see, we aren't even assuming that we know how to access the properties on the injected DOCUMENT. Instead, we are deferring to the Renderer to set the "title" property in case this is a non-trivial (or impossible) request in other rendering contexts. And, when we run this page, we can successfully set the document title:
Thinking in a platform-agnostic way is a huge mental leap when going from AngularJS 1.x to Angular 2. And, unfortunately, I don't see a lot of discussion about what that means from a practical sense for developers trying to execute on mundane tasks like setting the document title or cloning nodes (something I'm still experimenting with). But, at least now I have it in my mind to stay away from anything "browser" specific in the exported services in the Angular 2 framework.
Want to use code from this post? Check out the license.
Reader Comments
Why not use this?
https://angular.io/docs/js/latest/api/platform/browser/Title-class.html
@Sumigoma,
Good question. And, I'm not 100% convinced that my reasoning is correct. But, as I mentioned above, I did try to use that service - ng.platform.browser.Title - in my previous post:
www.bennadel.com/blog/3050-setting-the-window-document-title-in-angular-2-beta-9.htm
... but, I wasn't super happy with the way it was clearly referencing the *Browser* platform. The moment that I make a hard reference to the Browser platform, it means that the browser DOM has to be implemented or mocked out in other environments (such as if I want to render the component on the server). As such, I wanted to see if I could use only *common DOM* platform stuff to set the title. By doing that, my hope is that it's up the selected platform to implement all of the "common" operations, which means that such an approach will be viable in any rendering context.
But, of course, there's not that much out there yet about how to think about multiple rendering contexts. So, it is completely possible that my thinking is either incorrect or not warranted.