Setting Page Titles Natively With The Angular Router 🔥

Brandon Roberts - Feb 1 '22 - - Dev Community

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 { }
Enter fullscreen mode Exit fullscreen mode

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}`);
        }
      });
  }
}
Enter fullscreen mode Exit fullscreen mode

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 { }
Enter fullscreen mode Exit fullscreen mode

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 {}
Enter fullscreen mode Exit fullscreen mode

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'
  }
];
Enter fullscreen mode Exit fullscreen mode

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}`);
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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 {}
Enter fullscreen mode Exit fullscreen mode

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
  }
];
Enter fullscreen mode Exit fullscreen mode

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!

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Terabox Video Player