Router Helper Service
What Problems Does it Solve?
Section titled “What Problems Does it Solve?”The Router Helper Service addresses several common challenges in Angular routing:
-
Centralized API: It provides a single, consistent API for managing route parameters, query parameters, and route data, reducing the need to directly interact with the Angular Router in multiple places or the browsers location API.
-
Signal and Observable Support: It offers both signal-based and observable-based APIs, allowing developers to choose the best approach for their use case.
The Service
Section titled “The Service”Signal-Based APIs
Section titled “Signal-Based APIs”Route Parameters
Section titled “Route Parameters”// Single route parameter as a signalconst id = this.routerHelper.getRouteParam('id');console.log(id()); // Current value of 'id' parameter
// All route parameters as a signalconst params = this.routerHelper.getRouteParams();console.log(params()); // { id: '123', category: 'books' }
Query Parameters
Section titled “Query Parameters”// Single query parameter as a signalconst page = this.routerHelper.getQueryParam('page');console.log(page()); // Current value of 'page' parameter
// All query parameters as a signalconst queryParams = this.routerHelper.getQueryParams();console.log(queryParams()); // { page: '1', sort: 'desc' }
Route Data
Section titled “Route Data”// Access route data as a signalconst data = this.routerHelper.getRouteData();console.log(data()); // All resolved data from route
Observable-Based APIs
Section titled “Observable-Based APIs”Route Parameters
Section titled “Route Parameters”// Single route parameter as observablethis.routerHelper.getRouteParam$('id').subscribe((id) => { console.log(id); // Value changes over time});
// All route parameters as observablethis.routerHelper.getRouteParams$().subscribe((params) => { console.log(params); // All parameter changes});
Query Parameters
Section titled “Query Parameters”// Single query parameter as observablethis.routerHelper.getQueryParam$('page').subscribe((page) => { console.log(page); // Page changes over time});
// All query parameters as observablethis.routerHelper.getQueryParams$().subscribe((params) => { console.log(params); // All query parameter changes});
Route Data
Section titled “Route Data”// Access route data as observablethis.routerHelper.getRouteData$().subscribe((data) => { console.log(data); // Route data changes});
Navigation Events
Section titled “Navigation Events”// Navigation end events with URLthis.routerHelper.navigationEnd$.subscribe((url) => { console.log('Navigation completed to:', url);});
// Navigation start events with URLthis.routerHelper.navigationStart$.subscribe((url) => { console.log('Navigation starting to:', url);});
URL Management
Section titled “URL Management”// Current URL as a signalconst currentUrl = this.routerHelper.getCurrentUrl();console.log(currentUrl()); // Current URL
// Current URL as observablethis.routerHelper.getCurrentUrl$().subscribe((url) => { console.log('URL changed to:', url);});
Route Management
Section titled “Route Management”// Parent route as signalconst parentRoute = this.routerHelper.getParentRoute();console.log(parentRoute()); // Parent route information
// Parent route as observablethis.routerHelper.getParentRoute$().subscribe((route) => { console.log('Parent route changed:', route);});
// Previous route as signalconst previousRoute = this.routerHelper.getPreviousRoute();console.log(previousRoute()); // Previous route information
// Previous route as observablethis.routerHelper.getPreviousRoute$().subscribe((route) => { console.log('Previous route changed:', route);});
Navigation Methods
Section titled “Navigation Methods”// Navigate backthis.routerHelper.back();
// Navigate to external URLthis.routerHelper.navigateOutside('https://example.com');
// Open in new tab/windowthis.routerHelper.openNew('/some/route');
Complete Service Implementation
Section titled “Complete Service Implementation”import { Injectable, inject, signal, computed } from '@angular/core';import { Router, NavigationExtras, ActivatedRoute, NavigationEnd, NavigationStart, Params } from '@angular/router';import { Location, DOCUMENT } from '@angular/common';import { Observable, map, filter, shareReplay, startWith } from 'rxjs';import { toSignal } from '@angular/core/rxjs-interop';
@Injectable({ providedIn: 'root',})export class RouterHelperService { private router = inject(Router); private location = inject(Location); private document = inject(DOCUMENT); private activatedRoute = inject(ActivatedRoute);
// Navigation Events readonly navigationEnd$ = this.router.events.pipe( filter((event): event is NavigationEnd => event instanceof NavigationEnd), map((event) => event.url), shareReplay(1), );
readonly navigationStart$ = this.router.events.pipe( filter((event): event is NavigationStart => event instanceof NavigationStart), map((event) => event.url), shareReplay(1), );
// URL Management readonly getCurrentUrl$ = this.navigationEnd$.pipe(startWith(this.router.url));
readonly getCurrentUrl = toSignal(this.getCurrentUrl$, { initialValue: this.router.url });
// URL Management (Signals & Values) currentUrlSignal() { return computed(() => this.getCurrentUrl()); }
getCurrentUrlValue(): string { return this.getCurrentUrl(); }
// Domain and Full URL Management getDomain(): string { return this.document.location.origin; }
getFullUrl(): string { return this.document.location.href; }
getFullUrlForPath(path: string[]): string { const relativePath = path.join('/'); const fullPath = relativePath.startsWith('/') ? relativePath : `/${relativePath}`; return `${this.getDomain()}${fullPath}`; }
getFullUrlForCurrentRoute(): string { return `${this.getDomain()}${this.getCurrentUrlValue()}`; }
// Build full URLs with query parameters and fragments buildFullUrlWithQuery(path: string[], queryParams: Params): string { const relativeUrl = this.buildUrlWithQuery(path, queryParams); return `${this.getDomain()}${relativeUrl}`; }
buildFullUrlWithFragment(path: string[], fragment: string): string { const relativeUrl = this.buildUrlWithFragment(path, fragment); return `${this.getDomain()}${relativeUrl}`; }
buildFullUrlWithQueryAndFragment(path: string[], queryParams: Params, fragment: string): string { const relativeUrl = this.buildUrlWithQueryAndFragment(path, queryParams, fragment); return `${this.getDomain()}${relativeUrl}`; }
// Domain utilities getProtocol(): string { return this.document.location.protocol; }
getHostname(): string { return this.document.location.hostname; }
getPort(): string { return this.document.location.port; }
getHost(): string { return this.document.location.host; }
// Check if we're on localhost/development isLocalhost(): boolean { const hostname = this.getHostname(); return hostname === 'localhost' || hostname === '127.0.0.1' || hostname.startsWith('192.168.'); }
// Check if we're using HTTPS isSecure(): boolean { return this.getProtocol() === 'https:'; }
// Get base URL (domain + port if applicable) getBaseUrl(): string { return this.getDomain(); }
// Route Parameters (Observable) getRouteParam$(key: string): Observable<string | null> { return this.activatedRoute.params.pipe(map((params) => params[key] || null)); }
private routeParams = toSignal<Params, Params>(this.activatedRoute.params, { initialValue: {} });
getRouteParams$(): Observable<Params> { return this.activatedRoute.params; }
// Query Parameters (Observable) getQueryParam$(key: string): Observable<string | null> { return this.activatedRoute.queryParams.pipe(map((params) => params[key] || null)); }
private queryParams = toSignal<Params, Params>(this.activatedRoute.queryParams, { initialValue: {} });
// Fragment Management private fragment = toSignal(this.activatedRoute.fragment, { initialValue: null });
// Fragment (Observable) getFragment$(): Observable<string | null> { return this.activatedRoute.fragment; }
// Fragment (Signal) fragmentSignal() { return computed(() => this.fragment()); }
// Fragment (Value) getFragmentValue(): string | null { return this.fragment(); }
getQueryParams$(): Observable<Params> { return this.activatedRoute.queryParams; }
// Route Data (Observable) getRouteData$(): Observable<Record<string, unknown>> { return this.activatedRoute.data; }
private routeData = toSignal(this.activatedRoute.data, { initialValue: {} as Record<string, unknown> });
// Route Parameters (Signals) routeParamSignal(key: string) { return computed(() => this.routeParams()[key] || null); }
routeParamsSignal() { return computed(() => this.routeParams()); }
// Route Parameters (Values) getRouteParamValue(key: string): string | null { return this.routeParams()[key] || null; }
getRouteParamsValue(): Params { return this.routeParams(); }
// Query Parameters (Signals) queryParamSignal(key: string) { return computed(() => this.queryParams()[key] || null); }
queryParamsSignal() { return computed(() => this.queryParams()); }
// Query Parameters (Values) getQueryParamValue(key: string): string | null { return this.queryParams()[key] || null; }
getQueryParamsValue(): Params { return this.queryParams(); }
// Route Data (Signals) routeDataSignal() { return computed(() => this.routeData()); }
// Route Data (Values) getRouteDataValue(): Record<string, unknown> { return this.routeData(); }
// Parent Route Management getParentRoute$(): Observable<ActivatedRoute | null> { return this.activatedRoute.parent ? this.activatedRoute.parent.params.pipe(map(() => this.activatedRoute.parent)) : new Observable((observer) => observer.next(null)); }
private parentRoute = toSignal(this.getParentRoute$(), { initialValue: this.activatedRoute.parent });
parentRouteSignal() { return computed(() => this.parentRoute()); }
getParentRouteValue(): ActivatedRoute | null { return this.parentRoute(); }
// Previous Route Management private previousRoute = signal<string | null>(null);
getPreviousRoute$(): Observable<string | null> { return this.navigationEnd$.pipe( map((currentUrl) => { const previous = this.previousRoute(); this.previousRoute.set(currentUrl); return previous; }), ); }
previousRouteSignal() { return this.previousRoute.asReadonly(); }
getPreviousRouteValue(): string | null { return this.previousRoute(); }
// Navigation Methods navigateTo(path: string[], extras?: NavigationExtras): void { this.router.navigate(path, extras); }
// Navigation with Query Parameters navigateWithQuery(path: string[], queryParams: Params, extras?: NavigationExtras): void { this.router.navigate(path, { queryParams, ...extras, }); }
// Update only query parameters (preserve current route) updateQueryParams(queryParams: Params, merge = true): void { this.router.navigate([], { queryParams, queryParamsHandling: merge ? 'merge' : 'replace', relativeTo: this.activatedRoute, }); }
// Navigation with Fragment navigateWithFragment(path: string[], fragment: string, extras?: NavigationExtras): void { this.router.navigate(path, { fragment, ...extras, }); }
// Update only fragment (preserve current route) updateFragment(fragment: string): void { this.router.navigate([], { fragment, relativeTo: this.activatedRoute, }); }
// Navigation with both Query Parameters and Fragment navigateWithQueryAndFragment(path: string[], queryParams: Params, fragment: string, extras?: NavigationExtras): void { this.router.navigate(path, { queryParams, fragment, ...extras, }); }
// Remove specific query parameter removeQueryParam(key: string): void { const currentParams = this.getQueryParamsValue(); const updatedParams = { ...currentParams }; delete updatedParams[key];
this.router.navigate([], { queryParams: updatedParams, relativeTo: this.activatedRoute, }); }
// Clear all query parameters clearQueryParams(): void { this.router.navigate([], { queryParams: {}, relativeTo: this.activatedRoute, }); }
// Clear fragment clearFragment(): void { this.router.navigate([], { fragment: undefined, relativeTo: this.activatedRoute, }); }
// URL Construction Utilities buildUrlWithQuery(path: string[], queryParams: Params): string { const urlTree = this.router.createUrlTree(path, { queryParams }); return this.router.serializeUrl(urlTree); }
buildUrlWithFragment(path: string[], fragment: string): string { const urlTree = this.router.createUrlTree(path, { fragment }); return this.router.serializeUrl(urlTree); }
buildUrlWithQueryAndFragment(path: string[], queryParams: Params, fragment: string): string { const urlTree = this.router.createUrlTree(path, { queryParams, fragment }); return this.router.serializeUrl(urlTree); }
// Check if current route matches isCurrentRoute(path: string[]): boolean { const urlTree = this.router.createUrlTree(path); const currentUrlTree = this.router.parseUrl(this.router.url); return this.router.serializeUrl(urlTree) === this.router.serializeUrl(currentUrlTree); }
// Check if query parameter exists and has specific value hasQueryParam(key: string, value?: string): boolean { const currentValue = this.getQueryParamValue(key); if (value === undefined) { return currentValue !== null; } return currentValue === value; }
// Check if fragment matches hasFragment(fragment?: string): boolean { const currentFragment = this.getFragmentValue(); if (fragment === undefined) { return currentFragment !== null; } return currentFragment === fragment; }
back(): void { this.location.back(); }
navigateOutside(url: string): void { this.document.defaultView?.open(url, '_self'); }
openNew(path: string | string[]): void { const url = Array.isArray(path) ? path.join('/') : path; const fullUrl = new URL(url, this.document.location.origin).href; this.document.defaultView?.open(fullUrl, '_blank'); }}
Learn More with AI
Section titled “Learn More with AI”Create a Router Helper Service in Angular that simplifies route management by providing:1. Signal-based APIs for route parameters, query parameters, and route data2. Observable-based APIs for route parameters, query parameters, and route data3. Navigation event tracking4. URL management utilities5. Route management utilities including parent and previous route tracking6. Navigation methods for back navigation and external URLs
Include implementation details for handling:- Type safety- Route parameter consistency- Data and resolver naming conventions- Error handling and edge cases