diff --git a/adev/src/app/app.component.ts b/adev/src/app/app.component.ts index 646324b36a1..2287b167bdb 100644 --- a/adev/src/app/app.component.ts +++ b/adev/src/app/app.component.ts @@ -8,9 +8,11 @@ import {DOCUMENT, isPlatformBrowser} from '@angular/common'; import { + afterNextRender, ChangeDetectionStrategy, Component, inject, + Injector, OnInit, PLATFORM_ID, signal, @@ -23,13 +25,13 @@ import { getActivatedRouteSnapshotFromRouter, IS_SEARCH_DIALOG_OPEN, SearchDialog, - WINDOW, } from '@angular/docs'; import {Footer} from './core/layout/footer/footer.component'; import {Navigation} from './core/layout/navigation/navigation.component'; import {SecondaryNavigation} from './core/layout/secondary-navigation/secondary-navigation.component'; import {ProgressBarComponent} from './core/layout/progress-bar/progress-bar.component'; import {ESCAPE, SEARCH_TRIGGER_KEY} from './core/constants/keys'; +import {HeaderService} from './core/services/header.service'; @Component({ selector: 'adev-root', @@ -54,7 +56,7 @@ import {ESCAPE, SEARCH_TRIGGER_KEY} from './core/constants/keys'; export class AppComponent implements OnInit { private readonly document = inject(DOCUMENT); private readonly router = inject(Router); - private readonly window = inject(WINDOW); + private readonly headerService = inject(HeaderService); currentUrl = signal(''); displayFooter = signal(false); @@ -74,6 +76,8 @@ export class AppComponent implements OnInit { this.currentUrl.set(url); this.setComponentsVisibility(); this.displaySearchDialog.set(false); + + this.updateCanonicalLink(url); }); this.focusFirstHeadingOnRouteChange(); @@ -88,6 +92,10 @@ export class AppComponent implements OnInit { h1?.focus(); } + private updateCanonicalLink(absoluteUrl: string) { + this.headerService.setCanonical(absoluteUrl); + } + private setComponentsVisibility(): void { const activatedRoute = getActivatedRouteSnapshotFromRouter(this.router as any); diff --git a/adev/src/app/core/services/header.service.spec.ts b/adev/src/app/core/services/header.service.spec.ts new file mode 100644 index 00000000000..c9033b6733e --- /dev/null +++ b/adev/src/app/core/services/header.service.spec.ts @@ -0,0 +1,32 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {TestBed} from '@angular/core/testing'; + +import {HeaderService} from './header.service'; +import {link} from 'fs'; + +describe('HeaderService', () => { + let service: HeaderService; + + beforeEach(() => { + service = TestBed.inject(HeaderService); + }); + + it('setCanonical', () => { + // setCanonical assumes there is a preexisting element + const linkEl = document.createElement('link'); + linkEl.setAttribute('rel', 'canonical'); + document.querySelector('head')?.appendChild(linkEl); + + service.setCanonical('/some/link'); + expect(document.querySelector('link[rel=canonical]')!.getAttribute('href')).toBe( + 'https://angular.dev/some/link', + ); + }); +}); diff --git a/adev/src/app/core/services/header.service.ts b/adev/src/app/core/services/header.service.ts new file mode 100644 index 00000000000..ee21c0a8378 --- /dev/null +++ b/adev/src/app/core/services/header.service.ts @@ -0,0 +1,32 @@ +import {DOCUMENT} from '@angular/common'; +import {Injectable, inject} from '@angular/core'; + +const ANGULAR_DEV = 'https://angular.dev'; + +/** + * Information about the deployment of this application. + */ +@Injectable({providedIn: 'root'}) +export class HeaderService { + private readonly document = inject(DOCUMENT); + + /** + * Sets the canonical link in the header. + * It supposes the header link is already present in the index.html + * + * The function behave invariably and will always point to angular.dev, + * no matter if it's a specific version build + */ + setCanonical(absolutePath: string): void { + const pathWithoutFragment = this.normalizePath(absolutePath).split('#')[0]; + const fullPath = `${ANGULAR_DEV}/${pathWithoutFragment}`; + this.document.querySelector('link[rel=canonical]')?.setAttribute('href', fullPath); + } + + private normalizePath(path: string): string { + if (path[0] === '/') { + return path.substring(1); + } + return path; + } +} diff --git a/adev/src/index.html b/adev/src/index.html index 3e8444e50e9..f6d4c0eb48e 100644 --- a/adev/src/index.html +++ b/adev/src/index.html @@ -58,6 +58,8 @@ + +