Skip to content

Commit 7e0111f

Browse files
authored
docs: update guide viewer title when route params change (#33878)
`GuideViewer` subscribed to the route params to resolve the current guide, but only set the page title once in `ngOnInit`. Since the router reuses the component when only the `:id` param changes, navigating from one guide to another in-app would update the rendered guide while leaving the browser title pointing at the previous one. This isn't reachable today because guide-to-guide links in the markdown are absolute URLs that trigger a full page load, but it would surface as soon as an in-app link between guides is added. Changes: - Set the page title inside the params subscription, next to where the guide is resolved, so both stay in sync on every param emission. - Remove the now-redundant `ngOnInit` and the `OnInit` interface. - Tie the params subscription to the component's lifetime with `takeUntilDestroyed()`, matching the pattern used in `ComponentViewer`. - Switch the spec's mocked `params` to a `BehaviorSubject` and add tests covering the initial title and the title after a param change.
1 parent 6410ab4 commit 7e0111f

2 files changed

Lines changed: 38 additions & 21 deletions

File tree

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,25 @@
11
import {ComponentFixture, TestBed} from '@angular/core/testing';
2-
import {Observable} from 'rxjs';
3-
import {ActivatedRoute, provideRouter} from '@angular/router';
2+
import {BehaviorSubject, Observable} from 'rxjs';
3+
import {ActivatedRoute, Params, provideRouter} from '@angular/router';
44
import {GuideViewer} from './guide-viewer';
5+
import {ComponentPageTitle} from '../page-title/page-title';
56

67
const guideItemsId = 'getting-started';
78

8-
const mockActivatedRoute = {
9-
fragment: new Observable(observer => {
10-
observer.complete();
11-
}),
12-
params: new Observable(observer => {
13-
observer.next({id: guideItemsId});
14-
observer.complete();
15-
}),
16-
};
17-
189
describe('GuideViewer', () => {
1910
let fixture: ComponentFixture<GuideViewer>;
11+
let params: BehaviorSubject<Params>;
2012

2113
beforeEach(() => {
14+
params = new BehaviorSubject<Params>({id: guideItemsId});
15+
16+
const mockActivatedRoute = {
17+
fragment: new Observable(observer => {
18+
observer.complete();
19+
}),
20+
params,
21+
};
22+
2223
TestBed.configureTestingModule({
2324
providers: [provideRouter([]), {provide: ActivatedRoute, useValue: mockActivatedRoute}],
2425
});
@@ -33,4 +34,24 @@ describe('GuideViewer', () => {
3334
fixture.detectChanges();
3435
expect(component.guide()).toEqual(component.guideItems.getItemById(guideItemsId));
3536
});
37+
38+
it('should set the page title to the guide name', () => {
39+
const component = fixture.componentInstance;
40+
fixture.detectChanges();
41+
expect(TestBed.inject(ComponentPageTitle).title).toBe(
42+
component.guideItems.getItemById(guideItemsId)!.name,
43+
);
44+
});
45+
46+
it('should update the guide and page title when the route params change', () => {
47+
const component = fixture.componentInstance;
48+
fixture.detectChanges();
49+
50+
params.next({id: 'theming'});
51+
fixture.detectChanges();
52+
53+
const theming = component.guideItems.getItemById('theming')!;
54+
expect(component.guide()).toEqual(theming);
55+
expect(TestBed.inject(ComponentPageTitle).title).toBe(theming.name);
56+
});
3657
});

‎docs/src/app/pages/guide-viewer/guide-viewer.ts‎

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
* found in the LICENSE file at https://angular.dev/license
77
*/
88

9-
import {ChangeDetectionStrategy, Component, OnInit, inject, signal} from '@angular/core';
9+
import {ChangeDetectionStrategy, Component, inject, signal} from '@angular/core';
10+
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
1011
import {ActivatedRoute, Router} from '@angular/router';
1112
import {GuideItem, GuideItems} from '../../shared/guide-items/guide-items';
1213
import {Footer} from '../../shared/footer/footer';
@@ -26,7 +27,7 @@ import {DocViewer} from '../../shared/doc-viewer/doc-viewer';
2627
'class': 'docs-main-content',
2728
},
2829
})
29-
export class GuideViewer implements OnInit {
30+
export class GuideViewer {
3031
private readonly _componentPageTitle = inject(ComponentPageTitle);
3132
private readonly _router = inject(Router);
3233
guideItems = inject(GuideItems);
@@ -37,21 +38,16 @@ export class GuideViewer implements OnInit {
3738
const _route = inject(ActivatedRoute);
3839
const guideItems = this.guideItems;
3940

40-
_route.params.subscribe(p => {
41+
_route.params.pipe(takeUntilDestroyed()).subscribe(p => {
4142
const guideItem = guideItems.getItemById(p['id']);
4243
if (guideItem) {
4344
this.guide.set(guideItem);
45+
this._componentPageTitle.title = guideItem.name;
4446
}
4547

4648
if (!this.guide()) {
4749
this._router.navigate(['/guides']);
4850
}
4951
});
5052
}
51-
52-
ngOnInit(): void {
53-
if (this.guide() !== undefined) {
54-
this._componentPageTitle.title = this.guide()!.name;
55-
}
56-
}
5753
}

0 commit comments

Comments
 (0)