Setting Page Titles Natively With The Angular Router 🔥
Tags: angular, opensource
Published: 2022-02-01 17:48:56

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](https://twitter.com/twerske/status/1488313309644214272) 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.

```ts
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.

```ts
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.

```ts
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. 👏

```ts
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.

```ts
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.

```ts
@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`.

```ts
@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.

```ts
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 :heart: so other people will see it. Follow [me on Twitter](https://twitter.com/brandontroberts) and subscribe to my [YouTube Channel](https://youtube.com/brandonrobertsdev?sub_confirmation=1) for more content on [Angular](https://angular.io), [NgRx](https://ngrx.io), and more!