# All files / app/common/services seo.service.ts

100% Statements 47/47
100% Branches 17/17
100% Functions 4/4
100% Lines 44/44

Press n or j to go to the next uncovered block, b, p or k for the previous block.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    822x
    2x
    2x

    2x

    14x
    14x
    14x

    4x

    4x
    4x
    4x
    4x

    4x
    4x
    4x
    4x
    2x

    4x
    4x
    4x
    4x
    2x

    4x
    2x

    4x
    4x

    7x
    7x

    5x
    5x
    4x
    4x
    4x

    5x

    4x
    4x
    4x

    3x
    3x
    2x
    2x
    2x
    2x

    3x

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

    export interface SeoMetadata {
      title: string;
      description: string;
      keywords: string[];
      canonicalUrl?: string;
      imageUrl?: string;
      twitterHandle?: string;
    }

    @Injectable({ providedIn: 'root' })
    export class SeoService {
      constructor(
        private readonly meta: Meta,
        private readonly title: Title,
        @Inject(DOCUMENT) private readonly document: Document,
      ) {}

      setDefaultTags(metaData: SeoMetadata): void {
        this.title.setTitle(metaData.title);

        this.meta.updateTag({ name: 'description', content: metaData.description });
        this.meta.updateTag({ name: 'keywords', content: metaData.keywords.join(', ') });
        this.meta.updateTag({ name: 'robots', content: 'index, follow' });
        this.meta.updateTag({ name: 'theme-color', content: '#a33360' });

        // Open Graph
        this.meta.updateTag({ property: 'og:title', content: metaData.title });
        this.meta.updateTag({ property: 'og:description', content: metaData.description });
        this.meta.updateTag({ property: 'og:type', content: 'website' });
        if (metaData.imageUrl) {
          this.meta.updateTag({ property: 'og:image', content: metaData.imageUrl });
        }

        // Twitter
        this.meta.updateTag({ name: 'twitter:card', content: 'summary_large_image' });
        this.meta.updateTag({ name: 'twitter:title', content: metaData.title });
        this.meta.updateTag({ name: 'twitter:description', content: metaData.description });
        if (metaData.imageUrl) {
          this.meta.updateTag({ name: 'twitter:image', content: metaData.imageUrl });
        }
        if (metaData.twitterHandle) {
          this.meta.updateTag({ name: 'twitter:creator', content: metaData.twitterHandle });
        }

        const canonicalUrl = metaData.canonicalUrl ?? this.document?.location?.href ?? '';
        this.setCanonical(canonicalUrl);
      }

      setCanonical(url: string): void {
        const head = this.document.head;
        if (!head) return;

        let linkEl = head.querySelector("link[rel='canonical']") as HTMLLinkElement | null;
        if (!linkEl) {
          linkEl = this.document.createElement('link');
          linkEl.setAttribute('rel', 'canonical');
          head.appendChild(linkEl);
        }
        linkEl.setAttribute('href', url);
      }

      attachStructuredData(schema: Record<string, unknown>): void {
        const scriptId = 'pnb-structured-data';
        const head = this.document.head;
        if (!head) return;

        let scriptEl = this.document.getElementById(scriptId) as HTMLScriptElement | null;
        if (!scriptEl) {
          scriptEl = this.document.createElement('script');
          scriptEl.id = scriptId;
          scriptEl.type = 'application/ld+json';
          head.appendChild(scriptEl);
        }

        scriptEl.textContent = JSON.stringify(schema, null, 2);
      }
    }