Skip to content
Angular ng routing 4 min read

Programmatic Navigation

Declarative links with routerLink cover most navigation, but real applications frequently need to navigate from code: after a form submits, once a login succeeds, when a timer fires, or inside a guard. Angular’s Router service exposes two imperative methods for this — navigate and navigateByUrl — plus a rich set of navigation extras for query params, fragments, relative paths, and route state. This page shows how to use them correctly in modern standalone, signal-based Angular.

Injecting the Router

Navigation starts by injecting the Router service. In modern Angular you use the inject() function rather than constructor parameters, which keeps components terse and works inside field initializers.

import { Component, inject } from '@angular/core';
import { Router } from '@angular/router';

@Component({
  selector: 'app-login',
  standalone: true,
  template: `<button (click)="signIn()">Sign in</button>`,
})
export class LoginComponent {
  private readonly router = inject(Router);

  async signIn(): Promise<void> {
    // ...authenticate the user...
    await this.router.navigate(['/dashboard']);
  }
}

Both navigation methods return a Promise<boolean> that resolves to true when navigation succeeds, false if it was cancelled (for example by a guard), and rejects if an error occurs.

The two methods differ in how you describe the destination. navigate accepts an array of command segments that Angular composes into a URL, while navigateByUrl takes a single, fully-formed absolute URL string.

Aspectnavigate(commands, extras?)navigateByUrl(url, extras?)
InputArray of path segmentsAbsolute URL string
Relative navigationSupported via relativeToNot supported (always absolute)
Param/segment buildingAngular encodes each segmentYou build the string yourself
Best forComposing routes dynamicallyRedirecting to a known full URL
// Equivalent destinations
this.router.navigate(['/users', userId, 'profile']);
this.router.navigateByUrl(`/users/${userId}/profile`);

Prefer navigate with a commands array when any segment is dynamic. Angular URL-encodes each segment for you, so a value like john doe becomes john%20doe automatically instead of producing a broken URL.

Passing navigation extras

Both methods accept a NavigationExtras object as a second argument. This is how you attach query parameters, fragments, and other behavior to the navigation.

this.router.navigate(['/search'], {
  queryParams: { q: 'angular', page: 2 },
  fragment: 'results',
  queryParamsHandling: 'merge',
});

That produces /search?q=angular&page=2#results. The most common extras:

ExtraTypePurpose
queryParamsParamsAdds ?key=value pairs
fragmentstringAdds the #fragment
queryParamsHandling'merge' | 'preserve' | ''Keeps or merges existing query params
relativeToActivatedRouteAnchor for relative navigation
stateRecord<string, unknown>Transient data not shown in the URL
replaceUrlbooleanReplaces history instead of pushing
skipLocationChangebooleanNavigates without changing the URL bar

Relative navigation

By default, command arrays are resolved against the application root (absolute). To navigate relative to the current route, pass an ActivatedRoute via relativeTo. This is invaluable inside nested routes where you should not hardcode the full path.

import { Component, inject } from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router';

@Component({
  selector: 'app-user-detail',
  standalone: true,
  template: `<button (click)="editUser()">Edit</button>`,
})
export class UserDetailComponent {
  private readonly router = inject(Router);
  private readonly route = inject(ActivatedRoute);

  editUser(): void {
    // From /users/42 -> /users/42/edit
    this.router.navigate(['edit'], { relativeTo: this.route });
  }

  goToSibling(): void {
    // From /users/42 -> /users/99 using a parent-relative path
    this.router.navigate(['../99'], { relativeTo: this.route });
  }
}

The .. segment walks up one level just like a filesystem path, letting you reach sibling routes without knowing the absolute structure.

Passing transient state

Use state to hand data to the destination component without exposing it in the URL. It is ideal for things like a flash message or a pre-fetched object. The receiving component reads it from the current Navigation.

// Sender
this.router.navigate(['/orders', orderId], {
  state: { justCreated: true },
});

// Receiver
import { Component, inject } from '@angular/core';
import { Router } from '@angular/router';

@Component({
  selector: 'app-order',
  standalone: true,
  template: `@if (justCreated) { <p class="banner">Order placed!</p> }`,
})
export class OrderComponent {
  private readonly router = inject(Router);
  protected readonly justCreated =
    this.router.getCurrentNavigation()?.extras.state?.['justCreated'] ?? false;
}

state survives a page reload only if the browser restores it via the History API — treat it as best-effort UI data, never as a source of truth your component depends on.

Handling the navigation result

Because navigation is asynchronous and can be blocked by guards, inspect the resolved value when the outcome matters.

async checkout(): Promise<void> {
  const ok = await this.router.navigate(['/checkout']);
  if (!ok) {
    console.warn('Navigation to /checkout was blocked by a guard');
  }
}

Output:

Navigation to /checkout was blocked by a guard

Best practices

  • Reach for navigate with a commands array as the default; encode-by-hand strings only when the full URL is already known.
  • Always pass relativeTo: this.route for navigation inside nested or lazy-loaded feature routes to avoid brittle absolute paths.
  • Use queryParamsHandling: 'merge' when updating one filter so you don’t wipe out the user’s other query parameters.
  • Use replaceUrl: true after redirects (such as login) so the back button doesn’t return to a now-invalid page.
  • Treat state as transient UI data, not as a reliable data channel — fetch authoritative data via a resolver instead.
  • Await the returned Promise<boolean> whenever subsequent logic depends on the navigation actually completing.
Last updated June 14, 2026
Was this helpful?