55

What's the right way to change the app page title in browser when using the new router?

*Using Angular 2 CLI

TheUnreal
  • 23,434
  • 46
  • 157
  • 277

2 Answers2

79

The title can be set using the Title service

To get the title from the current route the data property could be used.

Plunker example

const routes: RouterConfig = [
    {
        path: '',
        redirectTo: '/login',
        pathMatch: 'full',
    },
    {
        path: 'login',
        component: LoginComponent,
        data: {title: 'Login'}
    },
    {
        path: 'home',
        component: HomeComponent,
        data: {title: 'Home'}
    },
    {
        path: 'wepays',
        component: WePaysComponent,
        data: {title: 'WePays'}
    }
];
export class AppComponent { 
  constructor(titleService: Title, router: Router) {
    router.events.subscribe(event => {
      if(event instanceof NavigationEnd) {
        var title = this.getTitle(router.routerState, router.routerState.root).join('-');
        console.log('title', title);
        titleService.setTitle(title);
      }
    });
  }

  // collect that title data properties from all child routes
  // there might be a better way but this worked for me
  getTitle(state, parent) {
    var data = [];
    if(parent && parent.snapshot.data && parent.snapshot.data.title) {
      data.push(parent.snapshot.data.title);
    }

    if(state && parent) {
      data.push(... this.getTitle(state, state.firstChild(parent)));
    }
    return data;
  }
}

Just found https://github.com/angular/angular/issues/9662#issuecomment-229034288 where a similar approach is demonstrated.

I also found https://toddmotto.com/dynamic-page-titles-angular-2-router-events with bit a more beautiful code.

George Chondrompilas
  • 3,167
  • 27
  • 35
Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • 1
    what's about first time load? – Victor Bredihin Sep 29 '17 at 12:09
  • What's the problem? – Günter Zöchbauer Sep 29 '17 at 12:10
  • 2
    router.events.subscribe... works great for router change, but it's not fired for the first time page load, so no title – Victor Bredihin Sep 29 '17 at 12:13
  • 1
    works for me this.activatedRoute.data .map(() => this.activatedRoute) .map((route) => { while (route.firstChild) route = route.firstChild; return route; }) .mergeMap((route) => route.data) .subscribe(event => { this.title = event['title']; this.titleService.setTitle(event['title']); }); – Victor Bredihin Sep 29 '17 at 12:28
  • moving the 3 lines inside `if(event instanceof NavigationEnd) { ... }` to a method and call the method from the constructor should work as well. – Günter Zöchbauer Sep 29 '17 at 14:19
  • 4
    Since RouterState doesn't expose a firstChild function, typescript will throw errors if this will be strictly typed. A better improvement would be the following code https://gist.github.com/nicu-chiciuc/7be9608893ca16fb324982f89d56995e. – nicusor Nov 13 '17 at 08:11
  • ONE LINE SOLUTION: I was able to access the value directly by simply using this : if(event instanceof NavigationEnd) { this.title = router.routerState.root.snapshot.firstChild.data.title; } – TRIKONINFOSYSTEMS Jul 21 '20 at 13:59
  • for first time load just move this block to constructor instead of onInit – minigeek Apr 28 '21 at 17:09
20

Following code (taken from: https://toddmotto.com/dynamic-page-titles-angular-2-router-events) works like a charm:

const routes: Routes = [{
  path: 'calendar',
  component: CalendarComponent,
  children: [
    { path: '', redirectTo: 'new', pathMatch: 'full' },
    { path: 'all', component: CalendarListComponent, data: { title: 'My Calendar' } },
    { path: 'new', component: CalendarEventComponent, data: { title: 'New Calendar Entry' } },
    { path: ':id', component: CalendarEventComponent, data: { title: 'Calendar Entry' } }
  ]
}];

and then the AppComponent

import { filter, map, mergeMap, tap } from 'rxjs/operators';

import { Component, OnInit } from '@angular/core';
import { Router, NavigationEnd, ActivatedRoute } from '@angular/router';
import { Title } from '@angular/platform-browser';

@Component({...})
export class AppComponent implements OnInit {
  constructor(
    private router: Router,
    private activatedRoute: ActivatedRoute,
    private titleService: Title
  ) {}
  ngOnInit() {
    this.router.events.pipe(
      filter(event => event instanceof NavigationEnd),
      map(() => this.activatedRoute),
      map(route => {
        while (route.firstChild) route = route.firstChild;
        return route;
      }),
      filter(route => route.outlet === 'primary'),
      mergeMap(route => route.data),
    ).subscribe((event) => this.titleService.setTitle(event['title']));
  }
}
rofrol
  • 14,438
  • 7
  • 79
  • 77
avi
  • 326
  • 2
  • 6
  • 15
    thats a really long lines of codes just to get the data: {title} of route – Joshua Fabillar May 18 '17 at 09:50
  • doesn't work with nesting/chaining titles (ParentRoute title [separator] ChildRoute title). The accepted answer is the complete solution. – MrCroft Oct 17 '17 at 14:07
  • Does it work for load `{path: ':shipmentId', canActivate: [SessionGuardService], loadChildren: 'app/edit-shipment/edit-shipment.module#EditShipmentModule', data: { title: PageTitle.editShipmentPage }}`, Noticed that the title does not get set when the route defined is coming from lazyloading – Frank Marcelo Feb 16 '18 at 00:33