143

Is there any npm module/ other way like React-Helmet that allows us to change page title as we route through our Angular application?

PS: I am using Angular 5.

7 Answers 7

271

You have a TitleService in Angular 5. Inject it in your component's constructor, and use the setTitle() method.

import {Title} from "@angular/platform-browser";

....

constructor(private titleService:Title) {
  this.titleService.setTitle("Some title");
}

Here are the docs from Angular: https://angular.io/guide/set-document-title

Sign up to request clarification or add additional context in comments.

4 Comments

Here is an update to the cookbook by Angular (works in Angular 6) : angular.io/guide/set-document-title
Does this work with nested components that each set their own individual title too?
If I understand your question correctly, yes, it does.
If you're just using the titleService in the constructor, you can omit the private and the this so stop it adding to the component this value
59

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.

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 { }

See here

2 Comments

not using ng14 but good to know for future purposes - excellent answer
This should be higher up, this is the best way for simple apps with only a few pages!
35

Here is tested way to set page title on Angular 8 but you can use it on Angular 5 as well. Once you set this you have to just set title on route file and all will set automatically.

import { Component } from '@angular/core';
import { ActivatedRoute, Router, NavigationEnd } from '@angular/router';
import { Title } from '@angular/platform-browser';
import { filter, map } from "rxjs/operators";

@Component({
    selector: 'app-root',
    templateUrl: './app.component.html'
})
export class AppComponent {

    constructor (private router: Router, private activatedRoute:    ActivatedRoute, private titleService: Title) {
        this.router.events.pipe(
            filter(event => event instanceof NavigationEnd),
            map(() => {
                let child = this.activatedRoute.firstChild;
                while (child) {
                    if (child.firstChild) {
                        child = child.firstChild;
                    } else if (child.snapshot.data &&    child.snapshot.data['title']) {
                        return child.snapshot.data['title'];
                    } else {
                        return null;
                    }
                }
                return null;
            })
        ).subscribe( (data: any) => {
            if (data) {
                this.titleService.setTitle(data + ' - Website Name');
            }
        });
    }

}

And on route file you can do something like this:

const routes: Routes = [
    {
        path: 'dashboard',
        component: DefaultDashboardComponent,
        data: {
            title: 'Dashboard'
        }
    }
];

6 Comments

I use your solution for the navigation. It works if I click on the links in the navigation but when I refresh the window (F5), it doesn't display the right title as it should be. Any idea to fix it?
@Anh-ThiDINH make sure you have all required modules imported on each module files if you are using lazyloading.
@Ankur how to make this "Website name" as dynamic from a constant file?
@BillNathan You can skip the route file data for the title and read that constant file on the Component file to set title with this.titleService.setTitle(your_variable_with_title_string)
This is why I love SO. Very elegant solution.
|
5

Here's a non-recursive solution that also fixes the replication of titles in case of path-less or component-less nested routes.

Make sure to add Angular's Title service to your application: https://angular.io/guide/set-document-title.

Then in your app.component.ts

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

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {

  constructor(
    private readonly router: Router,
    private readonly titleService: Title
  ) { }

  ngOnInit() {
    this.router.events.pipe(
      filter((event) => event instanceof NavigationEnd)
    ).subscribe(() => {
      this.titleService.setTitle(this.getNestedRouteTitles().join(' | '));
    });
  }

  getNestedRouteTitles(): string[] {
    let currentRoute = this.router.routerState.root.firstChild;
    const titles: string[] = [];

    while (currentRoute) {
      if (currentRoute.snapshot.routeConfig.data?.title) {
        titles.push(currentRoute.snapshot.routeConfig.data.title);
      }

      currentRoute = currentRoute.firstChild;
    }

    return titles;
  }

  getLastRouteTitle(): string {
    let currentRoute = this.router.routerState.root.firstChild;

    while (currentRoute.firstChild) {
      currentRoute = currentRoute.firstChild;
    }

    return currentRoute.snapshot.data?.title;
  }
}

Accessing the specific routeConfig.data prevents the repetition of the inherited title attribute.

And in main-routing.module.ts or any of your other routing files:

const routes: Routes = [
  {
    path: '',
    component: MainComponent,
    data: { title: 'MyApplication' },
    children: [
      {
        path: '',
        canActivateChild: [AuthGuard],
        children: [
          {
            path: 'dashboard',
            loadChildren: () => import('../dashboard/dashboard.module').then(m => m.DashboardModule),
            data: { title: 'Dashboard' }
          },
          {
            path: 'settings',
            loadChildren: () => import('../settings/settings.module').then(m => m.SettingsModule),
            data: { title: 'Settings' }
          }
        ]
      }
    ]
  }
];

Comments

2

I would prefer to add a wrapper class just to make sure that i will not change everywhere if import {Title} from "@angular/platform-browser"; changed in the upcoming release :) ... Maybe Something called "AppTitleService"

import { Injectable } from '@angular/core';
import { Title } from '@angular/platform-browser';

@Injectable({ providedIn: 'root' })
export class AppTitleService {

    constructor(private titleService: Title) { }

    getTitle() {
        this.titleService.getTitle();
    }
    setTitle(newTitle: string) {
        this.titleService.setTitle(newTitle);
    }
}

1 Comment

has a feel of good practice and modularity +1
1
// Component.ts
    
    import { Component, OnInit } from '@angular/core';
    import { Router, NavigationEnd, ActivatedRoute } from '@angular/router';
    import { filter } from 'rxjs/operators';
    
    @Component({
      selector: 'app-component',
      templateUrl: './component.html',
      styleUrls: ['./component.css']
    })
    export class Component implements OnInit {
    
      constructor(private router: Router, private route: ActivatedRoute) { }
    
      path: string[] = [];
      pathTitle: string;
    
      ngOnInit() {
        this.router.events.pipe(
          filter(event => event instanceof NavigationEnd)
        ).subscribe((event: NavigationEnd) => {
            this.path = event.url.substring(1).split('/');  //  URL => stackOverflow
            this.pathTitle = this.route.root.firstChild.snapshot.data.title; // data.title => stack Overflow
        });
      }
    
    // app-routing.module.ts
    const routes: Routes = [
      { 
        path: 'stackOverflow', 
        component: Component, 
        data: {title: 'stack Overflow'}
       }
    ];

Comments

-4

Today is 2022-10-14. Answers above including Angular's own doc are very good, but not the easiest/lightest, and sometimes tedious.

We have hundreds of components/pages, Angular 12+, title and <meta name="description" ...> are typically static, so the best way is hardcode without adding any packages:

<head>
  <title...>
  <meta ...>
  ...
</head>

While Chrome and Edge, including META SEO inspector show all title and meta from index.html and components, browsers display only the first title in the order of discovery.

Solution:

  1. Move <title> and <meta> of index.html to bottom
  2. Every component/page to have its own else default to index’s

Reasons:

  • <head> is no longer mandatory in HTML5 whom was released in 2014
  • <title> and <meta> can be placed anywhere
  • They are allowed to have multiple entries even for the same duplicated name
  • <title>, first discovered will be used for display so component/page’s shows
  • <meta>, all are used.
  • See this SO for details.

With one simple adjustment inside of index.html, Wa-La, all pages display the corresponding title. No need to introduce any new component/package and update hundreds of files, and SEO 100!

BTW, this is not just for Angular, apply to all others including .NET, Vue and React.

2 Comments

Your solution is great, but for Angular there is better solution This is what I am using for my Apps: - I set title and description meta as empty in index.html - I inject title and meta services in my appObserver service, like the auth service or the main bootstrap service - I made two functions setTitle() and resetTitle(), and same for description - In app component I call resetTitle() and resetDesc - In any component, I inject the appObserver service, and use setTitle() in constructor, and use resetTitle() in ngOnDestroy() This is the safest way and the best for SEO as Angular App
@IslamElKassas Your solution is like other above, great. But what's better than static content being static?

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.