When building applications with Angular, one common thing you should do is have the page title update after each successful navigation. This helps with accessibility and improves the navigation experience. This is something you've had to do manually in the past, but a recent feature added to the Angular Router coming in version 14 handles this natively, while allowing you to customize its behavior. This post shows you how to use the Angular Router's new built-in feature to for setting the page title after each successful navigation.
Setting the Page Title using Router Events ♻️
Previously, setting the page title with the Angular Router after each successful navigation was code you had to add to every project, or use an Angular library if provided. The example below shows some sample code of how you would do this:
First, you would use the data
property in the Route
object with a title
key to set the title for the page.
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { AboutComponent } from './about.component';
import { HomeComponent } from './home.component';
const routes: Routes = [
{
path: 'home',
component: HomeComponent,
data: { title: 'Home' }
},
{
path: 'about',
component: AboutComponent,
data: { title: 'About Me' }
}
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
Next, you would add code to your AppComponent
or some other root-level service that listens to the events from the Angular Router, looks for the title
property on the route, and uses it to set the page title.
import { Component } from '@angular/core';
import { Title } from '@angular/platform-browser';
import { Router, NavigationEnd, ActivatedRoute } from '@angular/router';
import { filter, map } from 'rxjs';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent {
constructor(
private router: Router,
private titleService: Title
) {}
ngOnInit() {
this.router.events
.pipe(
filter((event) => event instanceof NavigationEnd),
map(() => {
let route: ActivatedRoute = this.router.routerState.root;
let routeTitle = '';
while (route!.firstChild) {
route = route.firstChild;
}
if (route.snapshot.data['title']) {
routeTitle = route!.snapshot.data['title'];
}
return routeTitle;
})
)
.subscribe((title: string) => {
if (title) {
this.titleService.setTitle(`My App - ${title}`);
}
});
}
}
This same code would have to be copied to each project you worked on. Now, let's look at the new way page titles working natively with the Angular Router.
Using the built-in TitleStrategy 🤩
In Angular v14, there is a built-in strategy service for collecting the title from the route based on the primary router outlet, and setting the browser's page title.
Instead of using the data
object with the title
key, there is a new title
property on the route object itself for you to set the page title.
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { AboutComponent } from './about.component';
import { HomeComponent } from './home.component';
const routes: Routes = [
{
path: 'home',
component: HomeComponent,
title: "'My App - Home' // <-- Page title"
},
{
path: 'about',
component: AboutComponent,
title: "'My App - About Me' // <-- Page title"
}
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
And now you can delete the all that custom code from the AppComponent
that listens to router events. 👏
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent {}
And that's it! Now when you navigate to each route successfully, the page title is updated to the title defined in each route.
One thing to notice is that there isn't a way to define a prefix for each route, such as My App
. In larger applications, this could lead to duplication and inconsistencies with setting the page title.
And that's where you would use a custom title strategy.
Overriding The Global Title Strategy ✍️
The Angular Router also provides an abstract TitleStrategy
class you can use to extend the functionality of the default service provided.
First, you import the TitleStrategy
class from the @angular/router
package.
import { Injectable, NgModule } from '@angular/core';
import { Title } from '@angular/platform-browser';
import { RouterModule, RouterStateSnapshot, Routes, TitleStrategy } from '@angular/router';
const routes: Routes = [
{
path: 'home',
component: HomeComponent,
title: 'Home'
},
{
path: 'about',
component: AboutComponent,
title: 'About Me'
}
];
Next, you extend the class to implement a custom page title strategy that takes the title built from the routerState
and prefixes it with the application name.
@Injectable()
export class TemplatePageTitleStrategy extends TitleStrategy {
constructor(private readonly title: Title) {
super();
}
override updateTitle(routerState: RouterStateSnapshot) {
const title = this.buildTitle(routerState);
if (title !== undefined) {
this.title.setTitle(`My App - ${title}`);
}
}
}
Next, provide the TemplatePageTitleStrategy
as an override to the default TitleStrategy
.
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule],
providers: [
{
provide: TitleStrategy,
useClass: TemplatePageTitleStrategy
}
]
})
export class AppRoutingModule {}
Now each route provides only the page title itself, and the prefix for the entire application is only used in one place.
Using Resolvers to set page titles 🤖
Resolvers are a familiar concept with the Angular Router. You normally use them to fetch data before your route is loaded. You can also use a resolver to dynamically get the page title for an individual route.
The example below uses a CustomTitleResolver
to define the title for the /about
route.
import { Injectable, NgModule } from '@angular/core';
import { Title } from '@angular/platform-browser';
import { RouterModule, RouterStateSnapshot, Routes, TitleStrategy } from '@angular/router';
@Injectable({ providedIn: 'root' })
export class CustomTitleResolver {
resolve() {
return Promise.resolve('Custom About Me');
}
}
const routes: Routes = [
{
path: 'home',
component: HomeComponent,
title: 'Home'
},
{
path: 'about',
component: AboutComponent,
title: CustomTitleResolver
}
];
The title resolver can be used like any other resolver, allowing you to inject dependencies, perform some logic, or return an Observable or Promise the returns the page title string.
Summary 🪄
The new title strategy provides more functionality out of the box with the Angular Router that has been long requested, and gives developers more flexibility in handling page titles in a custom way.
GitHub Repo: https://github.com/brandonroberts/angular-router-page-titles
If you liked this, click the ❤️ so other people will see it. Follow me on Twitter and subscribe to my YouTube Channel for more content on Angular, NgRx, and more!
Top comments (17)
Didn't know about the new Title Strategy, thank you!
Great article! 👏
Great stuff! Now to convince my company to drop IE11 support so we can update :D
Thank you for the great article! 🧡🧡
Awesome ❤️_❤️
What about dynamic page title?
You can still do dynamic page titles
Is it possible to read the title out of the route config in a component? Want to use the TitleStrategy to append
${title} | ${companyName}
, but want to get the title in the component as well. I can see it in the snapshot data, but can't seem to access it:Hey Martin,
I was also trying to figure how to do it... It happens to be easier than I thought! :)
If you want to read this in a higher level component this is a bit tricky.
I came up with the following solution that combines router events with the
Title
service to create an observable:You could also call the
Title
service directly from your template. However, this is normally bad practice, because it would be called quite often.Great article. I had a question though. Will the 'setTitle' from the platform-browser package work for the same case if we set it the component level?
angular.io/api/platform-browser/Title
Thanks! Good question. My thought is that this would overwrite your title set at the component level because the components are activated before the navigation cycle is complete.
If you wanted to opt-out of this, you could probably set an empty
title
route property so it doesn't update the title.Thanks
It's so owesome, loved this strategy