or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

directives.mdhttp.mdi18n.mdindex.mdlocation.mdpipes.mdplatform.mdtesting.md
tile.json

location.mddocs/

Location and Navigation

Angular Common provides comprehensive services for managing browser navigation, URL manipulation, and routing support with multiple location strategies for different deployment scenarios and requirements.

Capabilities

Location Service

Location - URL and History Management

The main service for interacting with the browser's URL and navigation history.

/**
 * Service for interacting with browser URL and navigation history
 * Provides normalized API for path manipulation and history navigation
 */
export class Location implements OnDestroy {
  /** Get current path with optional hash inclusion */
  path(includeHash?: boolean): string;

  /** Get current history state */
  getState(): unknown;

  /** Check if current path equals provided path */
  isCurrentPathEqualTo(path: string, query?: string): boolean;

  /** Normalize URL by removing base href and cleaning path */
  normalize(url: string): string;

  /** Prepare URL for external use (add base href) */
  prepareExternalUrl(url: string): string;

  /** Navigate to path with optional query and state */
  go(path: string, query?: string, state?: any): void;

  /** Replace current history entry */
  replaceState(path: string, query?: string, state?: any): void;

  /** Navigate forward in history */
  forward(): void;

  /** Navigate backward in history */
  back(): void;

  /** Navigate to relative position in history */
  historyGo(relativePosition?: number): void;

  /** Subscribe to URL changes */
  onUrlChange(fn: (url: string, state: unknown) => void): VoidFunction;

  /** Subscribe to location events */
  subscribe(onNext: (value: PopStateEvent) => void, onThrow?: ((exception: any) => void) | null, onReturn?: (() => void) | null): SubscriptionLike;

  ngOnDestroy(): void;

  /** Static utility: Normalize query parameters */
  static normalizeQueryParams(params: string): string;

  /** Static utility: Join URL segments with slash */
  static joinWithSlash(start: string, end: string): string;

  /** Static utility: Remove trailing slash from URL */
  static stripTrailingSlash(url: string): string;
}

/** Factory function for Location service */
export function createLocation(): Location;

/** Interface for popstate events */
export interface PopStateEvent {
  pop?: boolean;
  state?: any;
  type?: string;
  url?: string;
}

Usage Examples:

@Injectable()
export class NavigationService {
  constructor(private location: Location) {}

  // Basic navigation
  navigateToUser(userId: number) {
    this.location.go(`/users/${userId}`);
  }

  navigateToUserWithQuery(userId: number, tab: string) {
    this.location.go(`/users/${userId}`, `tab=${tab}`);
  }

  // Replace current entry without adding to history
  replaceCurrentUrl(newPath: string) {
    this.location.replaceState(newPath);
  }

  // Navigate with state
  navigateWithState(path: string, data: any) {
    this.location.go(path, '', data);
  }

  // History navigation
  goBack() {
    this.location.back();
  }

  goForward() {
    this.location.forward();
  }

  goToRelativePosition(steps: number) {
    this.location.historyGo(steps);
  }

  // URL utilities
  getCurrentPath(): string {
    return this.location.path();
  }

  getCurrentPathWithHash(): string {
    return this.location.path(true);
  }

  isOnUserPage(): boolean {
    return this.location.isCurrentPathEqualTo('/users');
  }

  isOnSpecificUserPage(userId: number): boolean {
    return this.location.isCurrentPathEqualTo(`/users/${userId}`);
  }

  // URL manipulation
  normalizeUrl(url: string): string {
    return this.location.normalize(url);
  }

  prepareExternalUrl(internalUrl: string): string {
    return this.location.prepareExternalUrl(internalUrl);
  }

  // URL change monitoring
  monitorUrlChanges() {
    return this.location.onUrlChange((url, state) => {
      console.log('URL changed to:', url, 'with state:', state);
    });
  }

  // Subscribe to navigation events
  subscribeToNavigation() {
    return this.location.subscribe(event => {
      console.log('Navigation event:', event);
    });
  }
}

@Component({})  
export class NavigationComponent {
  private urlChangeUnsubscribe?: VoidFunction;

  constructor(private navigationService: NavigationService) {}

  ngOnInit() {
    // Monitor URL changes
    this.urlChangeUnsubscribe = this.navigationService.monitorUrlChanges();
  }

  ngOnDestroy() {
    if (this.urlChangeUnsubscribe) {
      this.urlChangeUnsubscribe();
    }
  }

  // Template methods
  goToHome() {
    this.navigationService.navigateToUser(1);
  }

  goBackInHistory() {
    this.navigationService.goBack();
  }
}

Location Strategies

LocationStrategy - Base Strategy

Abstract base class for location strategies.

/**
 * Abstract base class for location strategies
 * Defines interface for URL path management and history manipulation
 */
export abstract class LocationStrategy {
  /** Get current path with optional hash inclusion */
  abstract path(includeHash?: boolean): string;

  /** Prepare internal URL for external use */
  abstract prepareExternalUrl(internal: string): string;

  /** Get current history state */
  abstract getState(): unknown;

  /** Add new entry to history */
  abstract pushState(state: any, title: string, url: string, queryParams: string): void;

  /** Replace current history entry */
  abstract replaceState(state: any, title: string, url: string, queryParams: string): void;

  /** Navigate forward in history */
  abstract forward(): void;

  /** Navigate backward in history */
  abstract back(): void;

  /** Subscribe to popstate events */
  abstract onPopState(fn: LocationChangeListener): void;

  /** Get base href for the application */
  abstract getBaseHref(): string;

  /** Navigate to relative position in history (optional) */
  historyGo?(relativePosition: number): void;
}

/** Type for location change event listeners */
export type LocationChangeListener = (event: LocationChangeEvent) => any;

/** Interface for location change events */
export interface LocationChangeEvent {
  type: string;
  state: any;
}

PathLocationStrategy - HTML5 Strategy

Uses HTML5 pushState and popState APIs for clean URLs.

/**
 * Location strategy using HTML5 pushState and popState APIs
 * Provides clean URLs without hash fragments
 * Requires server-side support for deep linking
 */
export class PathLocationStrategy extends LocationStrategy implements OnDestroy {
  path(includeHash?: boolean): string;
  prepareExternalUrl(internal: string): string;
  getState(): unknown;
  pushState(state: any, title: string, url: string, queryParams: string): void;
  replaceState(state: any, title: string, url: string, queryParams: string): void;
  forward(): void;
  back(): void;
  onPopState(fn: LocationChangeListener): void;
  getBaseHref(): string;
  historyGo(relativePosition: number): void;
  
  ngOnDestroy(): void;
}

HashLocationStrategy - Hash-based Strategy

Uses URL hash fragments for client-side routing.

/**
 * Location strategy using URL hash fragments
 * Provides client-side routing without server-side configuration
 * URLs contain hash fragments (#/path)
 */
export class HashLocationStrategy extends LocationStrategy implements OnDestroy {
  path(includeHash?: boolean): string;
  prepareExternalUrl(internal: string): string;
  getState(): unknown;
  pushState(state: any, title: string, url: string, queryParams: string): void;
  replaceState(state: any, title: string, url: string, queryParams: string): void;
  forward(): void;
  back(): void;
  onPopState(fn: LocationChangeListener): void;
  getBaseHref(): string;
  
  ngOnDestroy(): void;
}

/** Injection token for application base href */
export const APP_BASE_HREF: InjectionToken<string>;

Usage Examples:

// HTML5 (Path) Strategy Configuration
@NgModule({
  providers: [
    { provide: LocationStrategy, useClass: PathLocationStrategy },
    { provide: APP_BASE_HREF, useValue: '/my-app/' }
  ]
})
export class AppModule {}

// Hash Strategy Configuration  
@NgModule({
  providers: [
    { provide: LocationStrategy, useClass: HashLocationStrategy }
  ]
})
export class AppModule {}

// Custom strategy implementation
@Injectable()
export class CustomLocationStrategy extends LocationStrategy {
  private baseHref = '/';

  path(includeHash?: boolean): string {
    // Custom path logic
    const path = window.location.pathname;
    return includeHash ? path + window.location.hash : path;
  }

  prepareExternalUrl(internal: string): string {
    return this.baseHref + internal;
  }

  getState(): unknown {
    return window.history.state;
  }

  pushState(state: any, title: string, url: string, queryParams: string): void {
    const fullUrl = url + (queryParams ? '?' + queryParams : '');
    window.history.pushState(state, title, fullUrl);
  }

  replaceState(state: any, title: string, url: string, queryParams: string): void {
    const fullUrl = url + (queryParams ? '?' + queryParams : '');
    window.history.replaceState(state, title, fullUrl);
  }

  forward(): void {
    window.history.forward();
  }

  back(): void {
    window.history.back();
  }

  onPopState(fn: LocationChangeListener): void {
    window.addEventListener('popstate', fn as EventListener);
  }

  getBaseHref(): string {
    return this.baseHref;
  }
}

// Using strategies in components
@Component({})
export class UrlExampleComponent {
  constructor(
    private locationStrategy: LocationStrategy,
    private location: Location
  ) {}

  displayUrlInfo() {
    console.log('Strategy path:', this.locationStrategy.path());
    console.log('Strategy base href:', this.locationStrategy.getBaseHref());
    console.log('Location normalized path:', this.location.path());
    
    // Compare external URLs
    const internal = '/users/123';
    const external = this.locationStrategy.prepareExternalUrl(internal);
    const locationExternal = this.location.prepareExternalUrl(internal);
    
    console.log('Internal URL:', internal);
    console.log('Strategy external URL:', external);
    console.log('Location external URL:', locationExternal);
  }
}

Platform Location

PlatformLocation - Platform Abstraction

Abstract class for platform-specific location implementations.

/**
 * Abstract platform-specific location implementation
 * Provides low-level access to browser location and history APIs
 */
export abstract class PlatformLocation {
  /** Get application base href */
  abstract getBaseHref(): string;

  /** Get current history state */
  abstract getState(): unknown;

  /** Subscribe to popstate events */
  abstract onPopState(fn: LocationChangeListener): void;

  /** Subscribe to hashchange events */
  abstract onHashChange(fn: LocationChangeListener): void;

  /** Current pathname */
  abstract get pathname(): string;

  /** Current search parameters */
  abstract get search(): string;  

  /** Current hash fragment */
  abstract get hash(): string;

  /** Add new entry to history */
  abstract pushState(state: any, title: string, url: string): void;

  /** Replace current history entry */
  abstract replaceState(state: any, title: string, url: string): void;

  /** Navigate forward in history */
  abstract forward(): void;

  /** Navigate backward in history */  
  abstract back(): void;

  /** Navigate to relative position in history */
  abstract historyGo(relativePosition: number): void;

  /** Get hostname */
  abstract get hostname(): string;

  /** Get protocol */
  abstract get protocol(): string;

  /** Get port */
  abstract get port(): string;

  /** Get full href */
  abstract get href(): string;
}

/** Browser-specific platform location implementation */
export class BrowserPlatformLocation extends PlatformLocation {
  getBaseHref(): string;
  getState(): unknown;
  onPopState(fn: LocationChangeListener): void;
  onHashChange(fn: LocationChangeListener): void;
  get pathname(): string;
  get search(): string;
  get hash(): string;
  pushState(state: any, title: string, url: string): void;
  replaceState(state: any, title: string, url: string): void;
  forward(): void;
  back(): void;
  historyGo(relativePosition: number): void;
  get hostname(): string;
  get protocol(): string;
  get port(): string;
  get href(): string;
}

/** Injection token for location initialization */
export const LOCATION_INITIALIZED: InjectionToken<Promise<any>>;

Usage Examples:

@Injectable()
export class PlatformLocationService {
  constructor(private platformLocation: PlatformLocation) {}

  // Get current URL components
  getCurrentUrlInfo() {
    return {
      pathname: this.platformLocation.pathname,
      search: this.platformLocation.search,
      hash: this.platformLocation.hash,
      hostname: this.platformLocation.hostname,
      protocol: this.platformLocation.protocol,
      port: this.platformLocation.port,
      href: this.platformLocation.href,
      baseHref: this.platformLocation.getBaseHref()
    };
  }

  // Monitor browser events
  setupEventListeners() {
    this.platformLocation.onPopState(event => {
      console.log('PopState event:', event);
    });

    this.platformLocation.onHashChange(event => {
      console.log('HashChange event:', event);
    });
  }

  // Direct history manipulation
  navigateToPath(path: string) {
    this.platformLocation.pushState(null, '', path);
  }

  replaceCurrentPath(path: string) {
    this.platformLocation.replaceState(null, '', path);
  }

  // History navigation
  goBackTwoSteps() {
    this.platformLocation.historyGo(-2);
  }

  goForwardOneStep() {
    this.platformLocation.historyGo(1);
  }
}

// Custom platform location for testing
export class MockPlatformLocation extends PlatformLocation {
  private _pathname = '/';
  private _search = '';
  private _hash = '';
  private _state: any = null;
  
  get pathname(): string { return this._pathname; }
  get search(): string { return this._search; }
  get hash(): string { return this._hash; }
  get hostname(): string { return 'localhost'; }
  get protocol(): string { return 'http:'; }
  get port(): string { return '4200'; }
  get href(): string { return `${this.protocol}//${this.hostname}:${this.port}${this.pathname}${this.search}${this.hash}`; }

  getBaseHref(): string {
    return '/';
  }

  getState(): unknown {
    return this._state;
  }

  onPopState(fn: LocationChangeListener): void {
    // Mock implementation
  }

  onHashChange(fn: LocationChangeListener): void {
    // Mock implementation  
  }

  pushState(state: any, title: string, url: string): void {
    this._state = state;
    this.setUrl(url);
  }

  replaceState(state: any, title: string, url: string): void {
    this._state = state;
    this.setUrl(url);
  }

  forward(): void {
    // Mock implementation
  }

  back(): void {
    // Mock implementation
  }

  historyGo(relativePosition: number): void {
    // Mock implementation
  }

  private setUrl(url: string): void {
    const [path, query] = url.split('?');
    const [pathname, hash] = path.split('#');
    
    this._pathname = pathname;
    this._search = query ? '?' + query : '';
    this._hash = hash ? '#' + hash : '';
  }
}

Location Utilities

/** Utility functions for URL manipulation */
export class Location {
  /** Normalize query parameters string */
  static normalizeQueryParams(params: string): string;

  /** Join URL segments with proper slash handling */
  static joinWithSlash(start: string, end: string): string;

  /** Remove trailing slash from URL */
  static stripTrailingSlash(url: string): string;
}

/** Utility functions for URL parsing and manipulation */
export function parseCookieValue(cookieStr: string, name: string): string | null;

Usage Examples:

@Injectable()
export class UrlUtilityService {
  
  // URL manipulation utilities
  buildCleanUrl(base: string, path: string): string {
    return Location.joinWithSlash(base, path);
  }

  cleanUrl(url: string): string {
    return Location.stripTrailingSlash(url);
  }

  normalizeQuery(queryString: string): string {
    return Location.normalizeQueryParams(queryString);
  }

  // Practical URL building
  buildApiUrl(endpoint: string): string {
    const base = '/api/v1';
    const cleanBase = Location.stripTrailingSlash(base);
    return Location.joinWithSlash(cleanBase, endpoint);
  }

  buildUrlWithQuery(path: string, params: { [key: string]: any }): string {
    const queryString = Object.entries(params)
      .filter(([_, value]) => value != null)
      .map(([key, value]) => `${key}=${encodeURIComponent(value)}`)
      .join('&');
    
    const normalizedQuery = Location.normalizeQueryParams(queryString);
    return path + (normalizedQuery ? '?' + normalizedQuery : '');
  }

  // Example usage
  buildUserUrl(userId: number, includeProfile: boolean): string {
    const path = Location.joinWithSlash('/users', userId.toString());
    
    if (includeProfile) {
      return this.buildUrlWithQuery(path, { include: 'profile' });
    }
    
    return Location.stripTrailingSlash(path);
  }
}

@Component({})
export class UrlBuilderComponent {
  constructor(private urlUtil: UrlUtilityService) {}

  examples() {
    // URL joining
    console.log(Location.joinWithSlash('/api/', '/users')); // '/api/users'
    console.log(Location.joinWithSlash('/api', 'users/')); // '/api/users/'
    console.log(Location.joinWithSlash('/api/', '/users/')); // '/api/users/'

    // URL cleaning
    console.log(Location.stripTrailingSlash('/api/users/')); // '/api/users'
    console.log(Location.stripTrailingSlash('/api/users')); // '/api/users'

    // Query normalization
    console.log(Location.normalizeQueryParams('a=1&b=2')); // 'a=1&b=2'
    console.log(Location.normalizeQueryParams('?a=1&b=2')); // 'a=1&b=2'

    // Utility service usage
    console.log(this.urlUtil.buildApiUrl('users')); // '/api/v1/users'
    console.log(this.urlUtil.buildUserUrl(123, true)); // '/users/123?include=profile'
  }
}

Types and Interfaces

// Event interfaces
export interface PopStateEvent {
  pop?: boolean;
  state?: any;
  type?: string;
  url?: string;
}

export interface LocationChangeEvent {
  type: string;
  state: any;
}

export type LocationChangeListener = (event: LocationChangeEvent) => any;

// Injection tokens
export const APP_BASE_HREF: InjectionToken<string>;
export const LOCATION_INITIALIZED: InjectionToken<Promise<any>>;

// Factory functions
export function createLocation(): Location;