Skip to content

Commit e81abdb

Browse files
JeanMecheAndrewKushnir
authored andcommitted
docs(docs-infra): add canonical link to each adev page (angular#56540)
PR Close angular#56540
1 parent 49b2e65 commit e81abdb

File tree

4 files changed

+76
-2
lines changed

4 files changed

+76
-2
lines changed

adev/src/app/app.component.ts

+10-2
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@
88

99
import {DOCUMENT, isPlatformBrowser} from '@angular/common';
1010
import {
11+
afterNextRender,
1112
ChangeDetectionStrategy,
1213
Component,
1314
inject,
15+
Injector,
1416
OnInit,
1517
PLATFORM_ID,
1618
signal,
@@ -23,13 +25,13 @@ import {
2325
getActivatedRouteSnapshotFromRouter,
2426
IS_SEARCH_DIALOG_OPEN,
2527
SearchDialog,
26-
WINDOW,
2728
} from '@angular/docs';
2829
import {Footer} from './core/layout/footer/footer.component';
2930
import {Navigation} from './core/layout/navigation/navigation.component';
3031
import {SecondaryNavigation} from './core/layout/secondary-navigation/secondary-navigation.component';
3132
import {ProgressBarComponent} from './core/layout/progress-bar/progress-bar.component';
3233
import {ESCAPE, SEARCH_TRIGGER_KEY} from './core/constants/keys';
34+
import {HeaderService} from './core/services/header.service';
3335

3436
@Component({
3537
selector: 'adev-root',
@@ -54,7 +56,7 @@ import {ESCAPE, SEARCH_TRIGGER_KEY} from './core/constants/keys';
5456
export class AppComponent implements OnInit {
5557
private readonly document = inject(DOCUMENT);
5658
private readonly router = inject(Router);
57-
private readonly window = inject(WINDOW);
59+
private readonly headerService = inject(HeaderService);
5860

5961
currentUrl = signal('');
6062
displayFooter = signal(false);
@@ -74,6 +76,8 @@ export class AppComponent implements OnInit {
7476
this.currentUrl.set(url);
7577
this.setComponentsVisibility();
7678
this.displaySearchDialog.set(false);
79+
80+
this.updateCanonicalLink(url);
7781
});
7882

7983
this.focusFirstHeadingOnRouteChange();
@@ -88,6 +92,10 @@ export class AppComponent implements OnInit {
8892
h1?.focus();
8993
}
9094

95+
private updateCanonicalLink(absoluteUrl: string) {
96+
this.headerService.setCanonical(absoluteUrl);
97+
}
98+
9199
private setComponentsVisibility(): void {
92100
const activatedRoute = getActivatedRouteSnapshotFromRouter(this.router as any);
93101

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*!
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.dev/license
7+
*/
8+
9+
import {TestBed} from '@angular/core/testing';
10+
11+
import {HeaderService} from './header.service';
12+
import {link} from 'fs';
13+
14+
describe('HeaderService', () => {
15+
let service: HeaderService;
16+
17+
beforeEach(() => {
18+
service = TestBed.inject(HeaderService);
19+
});
20+
21+
it('setCanonical', () => {
22+
// setCanonical assumes there is a preexisting element
23+
const linkEl = document.createElement('link');
24+
linkEl.setAttribute('rel', 'canonical');
25+
document.querySelector('head')?.appendChild(linkEl);
26+
27+
service.setCanonical('/some/link');
28+
expect(document.querySelector('link[rel=canonical]')!.getAttribute('href')).toBe(
29+
'https://angular.dev/some/link',
30+
);
31+
});
32+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import {DOCUMENT} from '@angular/common';
2+
import {Injectable, inject} from '@angular/core';
3+
4+
const ANGULAR_DEV = 'https://angular.dev';
5+
6+
/**
7+
* Information about the deployment of this application.
8+
*/
9+
@Injectable({providedIn: 'root'})
10+
export class HeaderService {
11+
private readonly document = inject(DOCUMENT);
12+
13+
/**
14+
* Sets the canonical link in the header.
15+
* It supposes the header link is already present in the index.html
16+
*
17+
* The function behave invariably and will always point to angular.dev,
18+
* no matter if it's a specific version build
19+
*/
20+
setCanonical(absolutePath: string): void {
21+
const pathWithoutFragment = this.normalizePath(absolutePath).split('#')[0];
22+
const fullPath = `${ANGULAR_DEV}/${pathWithoutFragment}`;
23+
this.document.querySelector('link[rel=canonical]')?.setAttribute('href', fullPath);
24+
}
25+
26+
private normalizePath(path: string): string {
27+
if (path[0] === '/') {
28+
return path.substring(1);
29+
}
30+
return path;
31+
}
32+
}

adev/src/index.html

+2
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@
5858
<link rel="manifest" href="/assets/icons/site.webmanifest" />
5959
<link rel="mask-icon" href="/assets/icons/safari-pinned-tab.svg" color="#e90464" />
6060
<link rel="shortcut icon" href="/assets/icons/favicon.ico" />
61+
<link rel="canonical" href="https://angular.dev">
62+
6163
<meta name="apple-mobile-web-app-title" content="Angular" />
6264
<meta name="application-name" content="Angular" />
6365
<meta name="msapplication-TileColor" content="#e90464" />

0 commit comments

Comments
 (0)