Repository: angular/angular
File: packages/common/src/location/location.ts (TypeScript)

Code before:
   * Removes a trailing slash from a URL string if needed.
   * Looks for the first occurrence of either `#`, `?`, or the end of the
   * line as `/` characters and removes the trailing slash if one exists.
   *
   * @param url URL string.
   *
   * @returns The URL string, modified if needed.
   */
  public static stripTrailingSlash: (url: string) => string = stripTrailingSlash;
}

@Injectable()
export class NavigationAdapterForLocation extends Location {
  private readonly navigation = inject(PlatformNavigation);
  private readonly destroyRef = inject(DestroyRef);

  constructor() {
    super(inject(LocationStrategy));

    const currentEntryChangeListener = () => {
      this._notifyUrlChangeListeners(this.path(true), this.getState());
    };
    this.navigation.addEventListener('currententrychange', currentEntryChangeListener);
    this.destroyRef.onDestroy(() => {
      this.navigation.removeEventListener('currententrychange', currentEntryChangeListener);
    });
  }

  override getState(): unknown {
    return this.navigation.currentEntry?.getState();
  }

  override replaceState(path: string, query: string = '', state: any = null): void {
    const url = this.prepareExternalUrl(path + normalizeQueryParams(query));
    // Use navigation API consistently for navigations. The "navigation API state"
    // field has no interaction with the existing "serialized state" field, which is what backs history.state
    this.navigation.navigate(url, {state, history: 'replace'});
  }

  override go(path: string, query: string = '', state: any = null): void {
    const url = this.prepareExternalUrl(path + normalizeQueryParams(query));
    // Use navigation API consistently for navigations. The "navigation API state"
    // field has no interaction with the existing "serialized state" field, which is what backs history.state
    this.navigation.navigate(url, {state, history: 'push'});
  }

  // Navigation.back/forward differs from history in how it traverses the joint session history
  // https://github.com/WICG/navigation-api?tab=readme-ov-file#correspondence-with-the-joint-session-history
  override back() {
    this.navigation.back();
  }

Review comment (suggestion):
optional: I like to break blocks like this into their own function like `registerNavigationListeners`

Code after:
   * Looks for the first occurrence of either `#`, `?`, or the end of the
   * line as `/` characters and removes the trailing slash if one exists.
   *
   * @param url URL string.
   *
   * @returns The URL string, modified if needed.
   */
  public static stripTrailingSlash: (url: string) => string = stripTrailingSlash;
}

export function createLocation() {
  return new Location(ɵɵinject(LocationStrategy as any));
}

function _stripBasePath(basePath: string, url: string): string {
  if (!basePath || !url.startsWith(basePath)) {
    return url;
  }
  const strippedUrl = url.substring(basePath.length);
  if (strippedUrl === '' || ['/', ';', '?', '#'].includes(strippedUrl[0])) {
    return strippedUrl;
  }
  return url;
}

function _stripIndexHtml(url: string): string {
  return url.replace(/\/index.html$/, '');
}

function _stripOrigin(baseHref: string): string {
  // DO NOT REFACTOR! Previously, this check looked like this:
  // `/^(https?:)?\/\//.test(baseHref)`, but that resulted in
  // syntactically incorrect code after Closure Compiler minification.
  // This was likely caused by a bug in Closure Compiler, but
  // for now, the check is rewritten to use `new RegExp` instead.
  const isAbsoluteUrl = new RegExp('^(https?:)?//').test(baseHref);
  if (isAbsoluteUrl) {
    const [, pathname] = baseHref.split(/\/\/[^\/]+/);
    return pathname;
  }
  return baseHref;
}