foundation: project guide, tooling, design tokens, bilingual shell and routing contracts
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
77
src/app/core/navigation/navigation.service.ts
Normal file
77
src/app/core/navigation/navigation.service.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import { computed, inject, Injectable, signal } from '@angular/core';
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
import { NavigationEnd, Router } from '@angular/router';
|
||||
import { filter } from 'rxjs';
|
||||
import { SITE_CONFIG } from '../content/site-config';
|
||||
import { otherLocale, type AppLocale } from '../i18n/locale';
|
||||
import { LocaleService } from '../i18n/locale.service';
|
||||
import { isAppRouteData } from '../routing/app-route-data';
|
||||
import { type RouteId } from '../routing/route-ids';
|
||||
import { routeCommands } from '../routing/route-paths';
|
||||
import { FOOTER_NAV, PRIMARY_NAV, type NavItem } from './navigation';
|
||||
|
||||
export interface ResolvedNavItem {
|
||||
readonly routeId: RouteId;
|
||||
readonly label: string;
|
||||
readonly link: unknown[];
|
||||
readonly children?: readonly ResolvedNavItem[];
|
||||
}
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class NavigationService {
|
||||
private readonly localeService = inject(LocaleService);
|
||||
private readonly router = inject(Router);
|
||||
private readonly activeRouteIdSignal = signal<RouteId>('home');
|
||||
|
||||
readonly activeRouteId = this.activeRouteIdSignal.asReadonly();
|
||||
readonly cvHref = SITE_CONFIG.cvAssetPath;
|
||||
readonly contactLink = computed(() => this.link('contact'));
|
||||
readonly primaryNav = computed(() => this.resolveItems(PRIMARY_NAV, this.localeService.locale()));
|
||||
readonly footerNav = computed(() => this.resolveItems(FOOTER_NAV, this.localeService.locale()));
|
||||
|
||||
constructor() {
|
||||
this.router.events
|
||||
.pipe(
|
||||
filter((event): event is NavigationEnd => event instanceof NavigationEnd),
|
||||
takeUntilDestroyed(),
|
||||
)
|
||||
.subscribe(() => this.syncFromRouter());
|
||||
|
||||
this.syncFromRouter();
|
||||
}
|
||||
|
||||
link(routeId: RouteId, locale?: AppLocale): unknown[] {
|
||||
return routeCommands(routeId, locale ?? this.localeService.locale());
|
||||
}
|
||||
|
||||
alternateLocaleLink(): unknown[] {
|
||||
const targetLocale = otherLocale(this.localeService.locale());
|
||||
const current = this.activeRouteId();
|
||||
const routeId = current === 'notFound' ? 'home' : current;
|
||||
return routeCommands(routeId, targetLocale);
|
||||
}
|
||||
|
||||
private resolveItems(items: readonly NavItem[], locale: AppLocale): ResolvedNavItem[] {
|
||||
return items.map((item) => ({
|
||||
routeId: item.routeId,
|
||||
label: item.label[locale],
|
||||
link: routeCommands(item.routeId, locale),
|
||||
children: item.children ? this.resolveItems(item.children, locale) : undefined,
|
||||
}));
|
||||
}
|
||||
|
||||
private syncFromRouter(): void {
|
||||
let snapshot = this.router.routerState.snapshot.root;
|
||||
|
||||
while (snapshot.firstChild) {
|
||||
snapshot = snapshot.firstChild;
|
||||
}
|
||||
|
||||
if (!isAppRouteData(snapshot.data)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.localeService.setLocale(snapshot.data.locale);
|
||||
this.activeRouteIdSignal.set(snapshot.data.routeId);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user