Dynamically add meta description based on route in Angular

AngularTypescriptAngular Ui-Router

Angular Problem Overview


I'm using Angular 5 to build a small brochure type website. Thus far, I have my routes set up, and the page title changes dynamically based on the activated route. I got this working using the instructions on this blog: https://toddmotto.com/dynamic-page-titles-angular-2-router-events

I'm currently storing my routes and titles in app.module.ts as such:

imports: [
    BrowserModule,
    RouterModule.forRoot([
      { 
        path: '', 
        component: HomeComponent,
        data: {
          title: 'Home'
        }
      },
      { 
        path: 'about', 
        component: AboutComponent,
        data: {
          title: 'About'
        } 
      },
      { 
        path: 'products-and-services', 
        component: ProductsServicesComponent,
        data: {
          title: 'Products & Services'
        }  
      },
      { 
        path: 'world-class-laundry', 
        component: LaundryComponent,
        data: {
          title: 'World Class Laundry'
        }  
      },
      { 
        path: 'contact', 
        component: ContactComponent,
        data: {
          title: 'Contact'
        }  
      },
      { 
        path: '**', 
        component: NotFoundComponent,
        data: {
          title: 'Page Not Found'
        }  
      }
    ])
  ],

I'd like to store my meta descriptions there as well, if adding them under data: would be easy enough.

I'm pulling in that title data with the following code, which is noted in the blog link above:

ngOnInit() {
    this.router.events
      .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']);
      });
  }

So my question is, is there a way to dynamically set the meta description using the same method? If there is a way to combine the page title and meta description function, that would be ideal.

I have very limited Angular training, so this might be a nooby question. I'm more of a designer/css/html kind of guy.

Angular Solutions


Solution 1 - Angular

First create a SEOService or Something like below:

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

@Injectable({
  provideIn: 'root' // Add this to ensure your SEO service will be app-wide available
})
export class SEOService {
  constructor(private title: Title, private meta: Meta) { }


  updateTitle(title: string) {
    this.title.setTitle(title);
  }

  updateOgUrl(url: string) {
    this.meta.updateTag({ name: 'og:url', content: url })
  }

  updateDescription(desc: string) {
    this.meta.updateTag({ name: 'description', content: desc })
  }
}

After injecting the SEOService in your component (app.component.ts preferably), set meta tags and title in OnInit method

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._seoService.updateTitle(event['title']);
        this._seoService.updateOgUrl(event['ogUrl']);
        //Updating Description tag dynamically with title
        this._seoService.updateDescription(event['title'] + event['description'])
      }); 
    }

Then configure your routes like

      { 
        path: 'about', 
        component: AboutComponent,
        data: {
          title: 'About',
          description:'Description Meta Tag Content',
          ogUrl: 'your og url'
        } 
      },

IMHO this is a clear way of dealing with meta tags. You can update facebook and twitter specific tags easier.

Solution 2 - Angular

Angular 6+ and RxJS 6+ solution for dynamically set title on route change

If/when you upgrade to Angular 6 this is the solution there.

This service will:

  • Update meta title on route change.
  • Option to override title for any reasons you want that.

Create/change your SEO/meta service to the following.

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

@Injectable({
    providedIn: 'root'
})
export class MetaService {
    constructor(
        private titleService: Title,
        private meta: Meta,
        private router: Router,
        private activatedRoute: ActivatedRoute
    ) { }

    updateMetaInfo(content, author, category) {
        this.meta.updateTag({ name: 'description', content: content });
        this.meta.updateTag({ name: 'author', content: author });
        this.meta.updateTag({ name: 'keywords', content: category });
    }

    updateTitle(title?: string) {
        if (!title) {
            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'] + ' | Site name');
                    });
        } else {
            this.titleService.setTitle(title + ' | Site name');
        }
    }
}

Import your service and call it in the contructor.

app.component.ts

constructor(private meta: MetaService) {
    this.meta.updateTitle();
}

And this still requires to format routes like this.

Route file.ts

{ 
    path: 'about', 
    component: AboutComponent,
    data: {
      title: 'About',
      description:'Description Meta Tag Content'
    } 
  },

Hope this will help for you and other people looking to update the title/meta dynamically in Angular 6.

Solution 3 - Angular

Title and Meta are providers that were introduced in Angular 4 and supposed to do this on both server and client side.

To create or update title tag and description meta tag, it is:

import { Meta, Title } from '@angular/platform-browser';

...

constructor(public meta: Meta, public title: Title, ...) { ... }

...

this.meta.updateTag({ name: 'description', content: description }); 
this.title.setTitle(title);

Solution 4 - Angular

IMO using data (title, description etc) in the Routes array is not so consistent, we can aggregate all the data in one place. Also in 2021, we can use common Angular tools out of the box:

seo-sitemap.ts

export const seoSitemap: ISitemapTag[] = [
  { customUrl: '/contact', title: null, description: 'Some description there', image: '/assets/path/to/image' },
{ customUrl: '/about', title: 'custom about title', description: 'Some description about', image: '/assets/path/to/another-image' }
];

export interface ISitemapTag {
  customUrl: string;
  title: string | null;
  description: string | null;
  image: string | null;
}

metatags.service.ts:

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

@Injectable()
export class MetatagsService {
  constructor(private title: Title, private meta: Meta) {}

  updateTitle(title: string) {
    this.title.setTitle(title);
  }

  updateTag(tag: MetaDefinition) {
    this.meta.updateTag(tag);
  }

  updateTags(tags: Array<MetaDefinition | null>) {
    tags.forEach((tag) => {
      tag && this.meta.updateTag(tag);
    });
  }
}

app.component.ts

import { MetatagsService } from './shared/services/metatags.service';
import { seoSitemap } from './seoSitemap';

constructor() {
    this.sub = this.router.events.subscribe((event: Event) => {
      if (event instanceof NavigationEnd) {
        this.setMetaTags(event);
      }
    });
}

ngOnDestroy() { this.sub && this.sub.unsubscribe() }

private setMetaTags(event: NavigationEnd) {
      const item = seoSitemap.find((i) => event.urlAfterRedirects === i.customUrl);
      if (item) {
        if (item.title) this.metatagsService.updateTitle(item.title);

        this.metatagsService.updateTags([
          item.description ? { name: 'description', content: item.description } : null,
          item.image ? { name: 'image', content: item.image } : null,
        ]);
        this.metatagsService.updateTag({ property: 'og:url', content: window.location.href });
      } else {
        this.metatagsService.updateTitle('Common title there');
      }
  }

Solution 5 - Angular

Since Angular pages are rendered at client side, its not possible for crawlers to detect meta tags. Most of the crawlers do not execute javascript at runtime due to which dynamic meta tags are not detected. This is true even for Facebook and Twitter.

Its required to use Angular Universal for Server Side Rendering or prerendering service e.g prerender.io

Edit:- I cam across a good youtube tutorial for this. Below is the link, https://www.youtube.com/watch?v=lncsmB5yfzE

Solution 6 - Angular

Here are the relevant parts from my project. (Angular 2/4)


app-routing.module.ts: > Routes:

... const appRoutes: Routes = [
    {
        path: 'path1', loadChildren: './path1#path1Module',
        data: {
            title: '...',
            description: '...',
            keywords: '...'
        }
    },
    {
        path: 'path2', loadChildren: './path2#path2Module',
        data: {
            title: '...',
            description: '...',
            keywords: '...'
        }
    } ...

app.component.ts (or your bootstrap component):

> imports:

// imports
import { Component, OnInit} from '@angular/core';
import { Router, ActivatedRoute, NavigationEnd } from '@angular/router';
import 'rxjs/add/operator/filter';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/mergeMap';
import { Title,Meta } from '@angular/platform-browser';

> constructor:

    // constructor:
        constructor(private router: Router,
                    private route: ActivatedRoute,
                    private titleService: Title, private meta: Meta) {}

> ngOnInit() method:

ngOnInit() {
       
        this.router.events
            .filter((event) => event instanceof NavigationEnd)
            .map(() => this.route)
            .map((route) => {
                while (route.firstChild) route = route.firstChild;
                return route;
            })
            .filter((route) => route.outlet === 'primary')
            .mergeMap((route) => route.data)
            .subscribe((event) => {
                this.updateDescription(event['description'], event['keywords'], event['title']);
            });

	}

> method that updates title and meta tags -> called from ngOnInit():

updateDescription(desc: string, keywords: string, title: string) {
    this.titleService.setTitle(title);
    this.meta.updateTag({ name: 'description', content: desc })
    this.meta.updateTag({ name: 'keywords', content: keywords })
    this.meta.updateTag({ name: 'og:title', content: title })
    this.meta.updateTag({ name: 'og:description', content: desc })
}

Hope it helps.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionKellenView Question on Stackoverflow
Solution 1 - AngularOkan AslankanView Answer on Stackoverflow
Solution 2 - AngularWalgermoView Answer on Stackoverflow
Solution 3 - AngularEstus FlaskView Answer on Stackoverflow
Solution 4 - AngularKurkov IgorView Answer on Stackoverflow
Solution 5 - AngularNikhilView Answer on Stackoverflow
Solution 6 - AngularJonathan ApplebaumView Answer on Stackoverflow