DEV Community

Cover image for Setting Page Titles Natively With The Angular Router 🔥
Brandon Roberts
Brandon Roberts

Posted on • Updated on

Setting Page Titles Natively With The Angular Router 🔥

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!

Top comments (17)

Collapse
 
alaindet profile image
Alain D'Ettorre

Didn't know about the new Title Strategy, thank you!

Collapse
 
rodrigokamada profile image
Rodrigo Kamada

Great article! 👏

Collapse
 
jessedebruijne profile image
Jesse de Bruijne

Great stuff! Now to convince my company to drop IE11 support so we can update :D

Collapse
 
azhe403 profile image
Azhe Kun

Thank you for the great article! 🧡🧡

Collapse
 
ronaldohoch profile image
Ronaldo Hoch

Awesome ❤️_❤️

Collapse
 
faradoxuzb profile image
faradoxuzb

What about dynamic page title?

Collapse
 
brandontroberts profile image
Brandon Roberts

You can still do dynamic page titles

Collapse
 
mtpultz profile image
Martin Pultz • Edited

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:

this.route.snapshot.data[Symbol('RouteTitle')])
Enter fullscreen mode Exit fullscreen mode
Collapse
 
fbanyai profile image
Flávio Banyai

Hey Martin,

I was also trying to figure how to do it... It happens to be easier than I thought! :)

this.route.routeConfig.title
Enter fullscreen mode Exit fullscreen mode
Collapse
 
marklagendijk profile image
Mark Lagendijk • Edited

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:

export class MyComponent{
  title$: Observable<string>;

  constructor(
    titleService: Title,
    router: Router
  ) {
    this.title$ = router.events.pipe(
      // Use startWith to get the initial title
      startWith(titleService.getTitle()),
      // The title is set after the last router event, so we need to wait until the next application cycle
      delay(0),
      map(() => titleService.getTitle())
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

You could also call the Title service directly from your template. However, this is normally bad practice, because it would be called quite often.

export class MyComponent{
  constructor(
    public titleService: Title,
  ) {
  }
}
Enter fullscreen mode Exit fullscreen mode
<h2>{{ titleService.getTitle()</h2>
Enter fullscreen mode Exit fullscreen mode
Collapse
 
sandeepsuvit profile image
Sandeep K Nair

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

Collapse
 
brandontroberts profile image
Brandon Roberts

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.

Collapse
 
gangadharreddy9 profile image
Gangadhar

Thanks

Collapse
 
chandrakantnaik789 profile image
chandrakant anik

It's so owesome, loved this strategy