diff --git a/docs/src/app/pages/component-category-list/component-category-list.html b/docs/src/app/pages/component-category-list/component-category-list.html index 3c0ea17085c0..186416885108 100644 --- a/docs/src/app/pages/component-category-list/component-category-list.html +++ b/docs/src/app/pages/component-category-list/component-category-list.html @@ -1,21 +1,27 @@ -
-
+
+
-@if (items.length > 0) { +@if (items().length > 0) {
- @for (component of items; track component) { - + @for (component of items(); track component.id) { +
- @if (section === 'components') { - + @if (section() === 'components') { + }
{{component.name}}
{{component.summary}}
diff --git a/docs/src/app/pages/component-category-list/component-category-list.ts b/docs/src/app/pages/component-category-list/component-category-list.ts index 9f0588459e0e..cfdb08ecb709 100644 --- a/docs/src/app/pages/component-category-list/component-category-list.ts +++ b/docs/src/app/pages/component-category-list/component-category-list.ts @@ -6,10 +6,10 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Component, OnDestroy, OnInit, inject} from '@angular/core'; +import {Component, inject, signal} from '@angular/core'; import {ActivatedRoute, RouterLink} from '@angular/router'; import {MatRipple} from '@angular/material/core'; -import {combineLatest, Subscription} from 'rxjs'; +import {combineLatest} from 'rxjs'; import { DocItem, @@ -19,6 +19,8 @@ import { import {NavigationFocus} from '../../shared/navigation-focus/navigation-focus'; import {ComponentPageTitle} from '../page-title/page-title'; +import {map, switchMap} from 'rxjs/operators'; +import {takeUntilDestroyed} from '@angular/core/rxjs-interop'; @Component({ selector: 'app-component-category-list', @@ -26,33 +28,54 @@ import {ComponentPageTitle} from '../page-title/page-title'; styleUrls: ['./component-category-list.scss'], imports: [NavigationFocus, RouterLink, MatRipple], }) -export class ComponentCategoryList implements OnInit, OnDestroy { +export class ComponentCategoryList { private readonly _docItems = inject(DocumentationItems); private readonly _componentPageTitle = inject(ComponentPageTitle); private readonly _route = inject(ActivatedRoute); - items: DocItem[] = []; - section = ''; - routeParamSubscription: Subscription = new Subscription(); - _categoryListSummary: string | undefined; - - ngOnInit() { - this.routeParamSubscription = combineLatest( - this._route.pathFromRoot.map(route => route.params), - Object.assign, - ).subscribe(async params => { - const sectionName = params['section']; - const section = SECTIONS[sectionName]; - this._componentPageTitle.title = section.name; - this._categoryListSummary = section.summary; - this.section = sectionName; - this.items = await this._docItems.getItems(sectionName); - }); - } + readonly items = signal([]); + readonly section = signal(''); + readonly _categoryListSummary = signal(undefined); + + constructor() { + // Combine all route parameters from root to current route into a single observable + // pathFromRoot gives us the entire route hierarchy (e.g., /docs/:category/:section) + combineLatest(this._route.pathFromRoot.map(route => route.params)) + .pipe( + // Merge all parameter objects into one, with child route params overriding parent params + // Example: [{category: 'components'}, {section: 'button'}] becomes {category: 'components', section: 'button'} + map(paramsArray => paramsArray.reduce((acc, curr) => ({...acc, ...curr}), {})), + + // Switch to a new observable when params change, canceling any pending requests + // This prevents race conditions if the user navigates quickly between sections + switchMap(params => { + // Extract the section name from route parameters + const sectionName = params['section']; + + // Look up section metadata from the SECTIONS configuration + const section = SECTIONS[sectionName]; + + // Update page title in browser tab/window + this._componentPageTitle.title = section.name; + + // Update component state with section summary (displayed in UI) + this._categoryListSummary.set(section.summary); + + // Store current section name + this.section.set(sectionName); + + // Fetch documentation items for this section from the service + // switchMap will cancel this request if route params change before completion + return this._docItems.getItems(sectionName); + }), - ngOnDestroy() { - if (this.routeParamSubscription) { - this.routeParamSubscription.unsubscribe(); - } + // Automatically unsubscribe when component is destroyed (no manual cleanup needed) + takeUntilDestroyed(), + ) + .subscribe(items => { + // Update the items signal with fetched documentation items + // This triggers change detection and updates the template + this.items.set(items); + }); } }