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 @@
+
+