Sep 5, 2019 - By Karl

What’s the first thing a user, a search engine or a bot sees when it visits your web page? Hint: It’s not your hero images. The most important information you need to include on each route in your Angular app is a custom Title tag as well as the meta Description.

If you are planning to get some SEO indexing for your app, these two are essential to make sure you aren’t getting penalized by Google page ranking algorithm for duplicate content. Let’s start by breaking it down step by step.

Add Page Data to Your Route

For starters, you’ll need to include the Title and Description for each page somewhere. My advice is to include the information directly where you are declaring your routes. Here’s an example of our AppComponent

{
    path: "get-started",
    loadChildren: () => import('../../pages/theme/get-started/get-started.module').then(m => m.GetStartedModule),
    data: {
        title: "Let's get started!",
        description: "Time to create an account and get started. You need to create an account before you can continue to use our application"
     }
}

This is a typical route object in Angular. The loadChildren parameter may be different based on the version of Angular you are using (this is 8). Now you’ll notice I added a data object here to include the information we need. You can add in anything here if you want. For now, we will include a Title and a Description.

Detect Route Data

Once that is complete, you’ll need to detect this information when loading a page with the router-outlet. To do that, let’s include some magic in our AppComponent to load the information we need:

export class AppComponent implements OnInit {

    private _defaultTitle: string = "App Title";
    private _defaultSuffix: string = " | App Title";

    constructor(
        private _route: ActivatedRoute,
        private _router: Router,
        private _title: Title, 
        private _meta: Meta
    ) {}

    public ngOnInit() {
        this._onRouteChange();
    }

    // Detect changes in URL and include data block
    protected _onRouteChange(): void {
        this._router.events.pipe(
            filter((event) => event instanceof NavigationEnd),
            map(() => this._route),
            map((route) => {
                while (route.firstChild) route = route.firstChild;
                return route;
            }),
            filter((route) => route.outlet === 'primary'),)
            .subscribe((event: any) => {
                if (event) this._setNavigation(event._routerState.snapshot.url, event.data._value);
            });
    }

    protected _setNavigation(url: string = null, data: any = null): void {
        this._setTitle(this.data);
        this._setDescription(this.data);
    }

    protected _setTitle(data: object): void {
        if('title' in data) this._title.setTitle(data['title'] + this._defaultSuffix);
        else this._title.setTitle(this._defaultTitle);
    }

    protected _setDescription(data: object): void {
        if ('description' in data) this._meta.updateTag({ name: 'description', content: data['description'] });
    }
}

I know this is a lot to digest, let’s break it down in pieces. First off, we need to detect changes to our URL to see when we are changing routes. The method _onRouteChange() - which is activated in the ngOnInit() listener - will detect a change and grab the data block we set earlier in our routes file:

// Detect changes in URL and include data block
protected _onRouteChange(): void {
    this._router.events.pipe(
        filter((event) => event instanceof NavigationEnd),
        map(() => this._route),
        map((route) => {
            while (route.firstChild) route = route.firstChild;
            return route;
        }),
        filter((route) => route.outlet === 'primary'),)
        .subscribe((event: any) => {
            if (event) this._setNavigation(event._routerState.snapshot.url, event.data._value);
        });
}

Once a change is detected, we pass the correct data block to the method _setNavigation(), which will use some Angular magic to set the Title tag and the meta Description tag in your HTML:

protected _setNavigation(url: string = null, data: any = null): void {
    this._setTitle(this.data);
    this._setDescription(this.data);
}

We separated the setting of the Title tag as well as the Description into their own methods to make things cleaner but feel free to move this around:

protected _setTitle(data: object): void {
    if('title' in data) this._title.setTitle(data['title'] + this._defaultSuffix);
    else this._title.setTitle(this._defaultTitle);
}

protected _setDescription(data: object): void {
    if ('description' in data) this._meta.updateTag({ name: 'description', content: data['description'] });
}

Finally, you’ll notice we included a _defaultTitle. This is optional, but we found that most web applications follow a similar pattern. Just make sure they are set as parameters in your AppComponent object:

private _defaultTitle: string = "App Title";
private _defaultSuffix: string = " | App Title";

And there you have it, custom Title and meta Description tags for each route in your Angular application. One thing to remember is that Google, Bing and any other search engine will only see the original Title tag you set in your index.html page. The end user will see the correct values in their browser.

You will still need to find a solution to make sure bots see the correct content. My advice? I’d recommend having a look at Feathery.io. It automatically creates a copy of your pages that Google can easily crawl automatically.

Have a look and good luck on your quest to master SEO!