how to add canonical link in angular 5

This applies to Angular 8 or 9, and will add canonical URLs to every page of your site.

  1. Create a canonical service:
// shared/canonical/canonical.service.ts


import { Injectable, Inject } from '@angular/core';
import { DOCUMENT } from '@angular/common';

@Injectable({
  providedIn: 'root'
})
export class CanonicalService {
  constructor(@Inject(DOCUMENT) private dom) {}

  setCanonicalURL(url?: string) {
    const canURL = url == undefined ? this.dom.URL : url;
    const link: HTMLLinkElement = this.dom.createElement('link');
    link.setAttribute('rel', 'canonical');
    this.dom.head.appendChild(link);
    link.setAttribute('href', canURL);
  }
}
  1. Reference the canonical service and call it in the app component
// app.component.ts
import { CanonicalService } from './shared/services/canonical/canonical.service';
...

ngOnInit() {
  this.canonicalService.setCanonicalURL();
}
...


If you want to add canonical link dynamically then create a simple moveToHead directive like this:

@Directive({
  selector: '[appMoveToHead]'
})
export class MoveToHeadDirective implements OnDestroy, OnInit {

  constructor(private renderer: Renderer2, 
              private elRef: ElementRef, 
              @Inject(DOCUMENT) private document: Document) {

  }

ngOnInit(): void {
    this.renderer.appendChild(this.document.head, this.elRef.nativeElement);
    this.renderer.removeAttribute(this.elRef.nativeElement, 'appmovetohead');
  }

  ngOnDestroy(): void {
    this.renderer.removeChild(this.document.head, this.elRef.nativeElement);
  }
}

Here, we are removing canonical Tag from the DOM when a user navigates to another page. We do this in ngOnDestroy() of MoveToHead directive. So the case of the previous canonical tag being present in the new page is handled.

Usage: in any component.ts

canonicalLink:string;
constructor(private sanitizer: DomSanitizer) { }
//In oninit or when your data is ready, generate canonical link
ngOnInit() {

    this.blogsService.getBlog(this.blogId).subscribe(data => {
       let canLink = "https://example.com/blog/"+data.blogId;
       // You can use pipe for sanitizing but lets do it here
       this.canonicalLink = this.sanitizer.bypassSecurityTrustResourceUrl(canLink);
    });
}

In any component.html template:

<div *ngIf="canonicalLink">
  <link  rel="canonical" appMoveToHead [attr.href]="canonicalLink" />
</div>

<div>
  <!-- Other template html -->
</div>

This way you can use data bindings and all other stuff, and treat meta, link, etc., just like any other element in your template. e.g. -

<link rel="amphtml" moveToHead [attr.href]="ampUrl">

This method works with Angular Universal (SSR) too.

Please refer below link -

https://github.com/angular/angular/issues/15776


I got the solution, create link service(ex: link.service.ts) & paste the below code :

import { Injectable, Optional, RendererFactory2, ViewEncapsulation, Inject } from '@angular/core';
import { DOCUMENT } from '@angular/platform-browser';

@Injectable()
export class LinkService {

constructor(
    private rendererFactory: RendererFactory2,
    @Inject(DOCUMENT) private document
) {
}

/**
 * Inject the State into the bottom of the <head>
 */
addTag(tag: LinkDefinition, forceCreation?: boolean) {

    try {
        const renderer = this.rendererFactory.createRenderer(this.document, {
            id: '-1',
            encapsulation: ViewEncapsulation.None,
            styles: [],
            data: {}
        });

        const link = renderer.createElement('link');
        const head = this.document.head;
        const selector = this._parseSelector(tag);

        if (head === null) {
            throw new Error('<head> not found within DOCUMENT.');
        }

        Object.keys(tag).forEach((prop: string) => {
            return renderer.setAttribute(link, prop, tag[prop]);
        });

        // [TODO]: get them to update the existing one (if it exists) ?
        renderer.appendChild(head, link);

    } catch (e) {
        console.error('Error within linkService : ', e);
    }
}

private _parseSelector(tag: LinkDefinition): string {
    // Possibly re-work this
    const attr: string = tag.rel ? 'rel' : 'hreflang';
    return `${attr}="${tag[attr]}"`;
}
}

 export declare type LinkDefinition = {
  charset?: string;
  crossorigin?: string;
  href?: string;
  hreflang?: string;
  media?: string;
  rel?: string;
  rev?: string;
  sizes?: string;
  target?: string;
  type?: string;
} & {
    [prop: string]: string;
};

import service in component :

import { LinkService } from '../link.service';

constructor(private linkService: LinkService) {

this.linkService.addTag( { rel: 'canonical', href: 'url here'} );
}

please refer below link :

https://github.com/angular/angular/issues/15776


Facing the same issue I searched around and found a guide on how to do this:

https://www.concretepage.com/angular/angular-title-service-and-canonical-url

It is using Angular 6 though, but I think it is backwards compatible to 5.

It basically suggests creating a Service (SEOService) for facilitating creating the canonical link whereever it is injected. It injects the Angular DOCUMENT object into the service, and creates the canonical link element as a HTMLLinkElement.

It takes care that the solution is prerendering/serverside rendering friendly - so if you are looking to better control your SEO for the pages in your application, I believe that this is what you want.

Here's a minimal rewrite of the service from the article:

seo.service.ts

import { Injectable, Inject } from '@angular/core';
import { DOCUMENT } from '@angular/common';

@Injectable({
  providedIn: 'root'
})
export class SeoService { 
   
  constructor(@Inject(DOCUMENT) private doc) {}
  
  createLinkForCanonicalURL() {
     let link: HTMLLinkElement = this.doc.createElement('link');
     link.setAttribute('rel', 'canonical');
     this.doc.head.appendChild(link);
     link.setAttribute('href', this.doc.URL);
   }
} 

And here's a rewrite of the component consuming the service:

data.component.ts

import { Component, OnInit } from '@angular/core';
import { SeoService } from './seo.service';

@Component({
  selector: 'app-data',
  templateUrl: './data.component.html'
})
export class DataComponent implements OnInit {
  
  constructor(private seoService: SeoService) { }
  
  ngOnInit() {
    this.createLinkForCanonicalURL();
  }
  
  createLinkForCanonicalURL() {
    this.seoService.createLinkForCanonicalURL();
  } 
}

You could simply have the createLinkForCanonicalURL() method take a optional parameter of the URL that you would want as a canonical reference for the page, for full control.