mathias.hurler revised this gist 1 day ago. Go to revision
1 file changed, 136 insertions
seo.service.ts(file created)
| @@ -0,0 +1,136 @@ | |||
| 1 | + | import { Injectable, inject } from "@angular/core"; | |
| 2 | + | import { Title, Meta } from "@angular/platform-browser"; | |
| 3 | + | import { DOCUMENT } from "@angular/common"; | |
| 4 | + | import { RendererFactory2 } from "@angular/core"; | |
| 5 | + | import { SeoData } from "@core/models/seo.model"; | |
| 6 | + | ||
| 7 | + | @Injectable({ providedIn: "root" }) | |
| 8 | + | export class SeoService { | |
| 9 | + | private titleService = inject(Title); | |
| 10 | + | private metaService = inject(Meta); | |
| 11 | + | private document = inject(DOCUMENT); | |
| 12 | + | private rendererFactory = inject(RendererFactory2); | |
| 13 | + | private renderer = | |
| 14 | + | this.rendererFactory.createRenderer(null, null); | |
| 15 | + | ||
| 16 | + | // Constants for metadata | |
| 17 | + | private readonly BRAND = "Hurler Webdesign"; | |
| 18 | + | private readonly FALLBACK_IMAGE = | |
| 19 | + | 'https://hurler-webdesign.de/assets/og-default.jpg'; | |
| 20 | + | private readonly DEFAULT_TYPE = "website"; | |
| 21 | + | ||
| 22 | + | /** | |
| 23 | + | * Updates SEO metadata with the provided data. | |
| 24 | + | * | |
| 25 | + | * @param data - The SeoData object containing title, description, type, and image. | |
| 26 | + | * @param canonicalPath - Optional parameter for the canonical URL path. | |
| 27 | + | */ | |
| 28 | + | updateMetadata(data: SeoData, canonicalPath?: string) { | |
| 29 | + | const fullTitle = `${data.title} | ${this.BRAND}`; | |
| 30 | + | const url = `https://hurler-webdesign.de${canonicalPath || ""}`; | |
| 31 | + | ||
| 32 | + | // Update title and meta description | |
| 33 | + | this.titleService.setTitle(fullTitle); | |
| 34 | + | this.metaService.updateTag({ name: "description", content: data.description }); | |
| 35 | + | ||
| 36 | + | // Open Graph metadata | |
| 37 | + | this.metaService.updateTag({ | |
| 38 | + | property: "og:title", | |
| 39 | + | content: fullTitle, | |
| 40 | + | }); | |
| 41 | + | this.metaService.updateTag({ | |
| 42 | + | property: "og:description", | |
| 43 | + | content: data.description, | |
| 44 | + | }); | |
| 45 | + | this.metaService.updateTag({ | |
| 46 | + | property: "og:type", | |
| 47 | + | content: data.type || this.DEFAULT_TYPE, | |
| 48 | + | }); | |
| 49 | + | this.metaService.updateTag({ | |
| 50 | + | property: "og:image", | |
| 51 | + | content: data.image || this.FALLBACK_IMAGE, | |
| 52 | + | }); | |
| 53 | + | this.metaService.updateTag({ property: "og:url", content: url }); | |
| 54 | + | ||
| 55 | + | // Twitter card metadata | |
| 56 | + | this.metaService.updateTag({ | |
| 57 | + | name: "twitter:card", | |
| 58 | + | content: "summary_large_image", | |
| 59 | + | }); | |
| 60 | + | this.metaService.updateTag({ | |
| 61 | + | name: "twitter:title", | |
| 62 | + | content: fullTitle, | |
| 63 | + | }); | |
| 64 | + | this.metaService.updateTag({ | |
| 65 | + | name: "twitter:description", | |
| 66 | + | content: data.socialsDescription || data.description, | |
| 67 | + | }); | |
| 68 | + | this.metaService.updateTag({ name: "twitter:url", content: url }); | |
| 69 | + | this.metaService.updateTag({ | |
| 70 | + | name: "twitter:image", | |
| 71 | + | content: data.image || this.FALLBACK_IMAGE, | |
| 72 | + | }); | |
| 73 | + | ||
| 74 | + | // Update canonical URL if provided | |
| 75 | + | if (canonicalPath) { | |
| 76 | + | this.updateCanonicalUrl(url); | |
| 77 | + | } | |
| 78 | + | ||
| 79 | + | // Set local business schema.org metadata | |
| 80 | + | this.setLocalBusinessSchema(); | |
| 81 | + | } | |
| 82 | + | ||
| 83 | + | /** | |
| 84 | + | * Updates the canonical URL for the given page. | |
| 85 | + | * | |
| 86 | + | * @param url - The new canonical URL. | |
| 87 | + | */ | |
| 88 | + | private updateCanonicalUrl(url: string) { | |
| 89 | + | let link: HTMLLinkElement = | |
| 90 | + | this.document.querySelector("link[rel='canonical']") || | |
| 91 | + | this.renderer.createElement("link"); | |
| 92 | + | this.renderer.setAttribute(link, "rel", "canonical"); | |
| 93 | + | this.renderer.setAttribute(link, "href", url); | |
| 94 | + | if (!this.document.head.contains(link)) { | |
| 95 | + | this.renderer.appendChild(this.document.head, link); | |
| 96 | + | } | |
| 97 | + | } | |
| 98 | + | ||
| 99 | + | /** | |
| 100 | + | * Sets the local business schema.org JSON-LD script in the head of the document. | |
| 101 | + | */ | |
| 102 | + | private setLocalBusinessSchema() { | |
| 103 | + | const oldScript = this.document.getElementById('schema-org-data'); | |
| 104 | + | if (oldScript) this.renderer.removeChild(this.document.head, oldScript); | |
| 105 | + | ||
| 106 | + | const schema = { | |
| 107 | + | "@context": "https://schema.org", | |
| 108 | + | "@type": "WebDesignService", | |
| 109 | + | "name": this.BRAND, | |
| 110 | + | "url": "https://hurler-webdesign.de", | |
| 111 | + | "logo": "https://hurler-webdesign.de/assets/logo.png", | |
| 112 | + | "image": "https://hurler-webdesign.de/assets/office.jpg", | |
| 113 | + | "description": "Spezialist für schnelle Webseiten ohne WordPress für Handwerk & Vereine. Wir bieten maßgeschneidertes Webdesign für eine schnelle und sichere Online-Präsenz.", | |
| 114 | + | "address": { | |
| 115 | + | "@type": "PostalAddress", | |
| 116 | + | "streetAddress": "Untermagerbein 30", | |
| 117 | + | "addressLocality": "Mönchsdeggingen", | |
| 118 | + | "postalCode": "86751", | |
| 119 | + | "addressCountry": "DE" | |
| 120 | + | }, | |
| 121 | + | "geo": { | |
| 122 | + | "@type": "GeoCoordinates", | |
| 123 | + | "latitude": 48.7506, | |
| 124 | + | "longitude": 10.5773 | |
| 125 | + | }, | |
| 126 | + | "areaServed": ["Nördlingen", "Donauwörth", "Augsburg", "Bayern"], | |
| 127 | + | "telephone": "+49 171 8084830", | |
| 128 | + | "priceRange": "€€" | |
| 129 | + | }; | |
| 130 | + | const script = this.renderer.createElement('script'); | |
| 131 | + | script.type = 'application/ld+json'; | |
| 132 | + | script.id = 'schema-org-data'; | |
| 133 | + | script.text = JSON.stringify(schema); | |
| 134 | + | this.renderer.appendChild(this.document.head, script); | |
| 135 | + | } | |
| 136 | + | } | |
Newer
Older