how to add canonical link in angular 5
Asked Answered
W

5

16

How to add canonical link in angular 5 dynamically

<link rel="canonical" href="http://foobar.com/gotcah"/>
Watterson answered 7/6, 2018 at 9:34 Comment(1)
i think the href is a leak to the reception of this questionRizal
W
5

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

Watterson answered 7/6, 2018 at 10:11 Comment(2)
Could it be, that the parseSelector Method in the class isnt neccessary at all?Kesley
there are 3 variables and 1 method that is not used in your codeSilicone
A
32

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.

Antilogy answered 12/6, 2018 at 22:32 Comment(8)
this should be the Accepted answerAspersorium
Is it problematic that the canonical Tag was not removed when the user navigates to another page?Ballou
@ThomasZoé Probably not, since it will be only a client-side issue.Chondriosome
But I belive previous canonical tag should be removed from DOM before adding another on navigation, isn't it?Radiolucent
Am facing the issue of canonical is coming as Http link but my site is in Https. How to resolve this issue.Akin
@AbijithAjayan, are you adding it like the above example? Through the angular DOCUMENT.URL object? That object is constructed in the client browser, which means that it should be aware of the protocol that is used (Documentation). If it is set in the html staticly, then you will need to correct it manually.Antilogy
@AndreasLorenzen, Yes am following like this above example. But When am going to view source, canonical Url is showing with Http link. But my actual website is in Https. So the issue is raising as URL Canonicalized.Akin
As many others have said THIS should be the accepted answer.Zebe
W
5

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

Watterson answered 7/6, 2018 at 10:11 Comment(2)
Could it be, that the parseSelector Method in the class isnt neccessary at all?Kesley
there are 3 variables and 1 method that is not used in your codeSilicone
B
4

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();
}
...

Bootless answered 23/1, 2020 at 1:27 Comment(1)
This will add a canonical link, even if there exists one already, resulting in multiple canonical links. Better to check first if there is a link present, and if so, update it.Daria
P
3

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

Prom answered 6/9, 2019 at 7:15 Comment(0)
H
3

To destroy existing link call this just before creating new one:

destroyLinkForCanonicalURL() {
    const els = this.document.querySelectorAll('link[rel=\'canonical\']');
    for (let i = 0, l = els.length; i < l; i++) {
      const el = els[i];
      el.remove();
    }
  }

Works perfect with this solution.

It also prevents duplicating links in SSR mode.

Hindemith answered 25/1, 2022 at 21:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.