Question

How to Pass Data to Angular Routed Components

typescriptangular

Question

In Angular, how can you navigate from one routed component to another while passing data along, without rendering the target component as a child directive?

For example, suppose FirstComponent contains a button that should navigate to SecondComponent and make some values available there.

<div class="button" (click)="routeWithData()">Pass data and route</div>
export class FirstComponent {
  constructor(private router: Router) {}

  property1!: number;
  property2!: string;
  property3!: TypeXY; // custom object, not a primitive

  routeWithData() {
    // navigate here
  }
}

A simple navigation might look like this:

this.router.navigate(['SecondComponent']);

And passing simple values in the URL might look like this:

this.router.navigate(['SecondComponent', { p1: this.property1, p2: this.property2 }]);

The concern is that URL-based routing works well for primitives, but not for complex objects like property3.

Another option would be to render the target component directly and pass data with @Input():

<app-second [p3]="property3"></app-second>

But that is not the goal here, because the requirement is to route to the component, not nest it inside the current template.

A shared service also seems possible:

  • Store data in the service before navigating
  • Read the data from the service in SecondComponent

Is using a service the usual or most elegant solution, or are there better ways to pass data between routed Angular components with as little code as possible?

Short Answer

By the end of this page, you will understand the main ways to pass data between Angular routed components, when to use each one, and why some methods work better for simple values while others are better for complex objects. You will also learn the trade-offs between route parameters, query parameters, router navigation state, and shared services.

Concept

In Angular, routed components are created by the router, not by a parent component template. That means @Input() binding does not work the same way it does for nested components.

When you navigate to another route, Angular creates that component from the route configuration. Because of that, data must be passed through mechanisms the router understands, or through shared application state.

The common options are:

  • Route parameters: good for required, URL-friendly values like IDs
  • Query parameters: good for optional filters, search terms, pagination, and small primitive values
  • Navigation state: good for temporary data passed during navigation without putting it in the URL
  • Shared service: good for complex objects, shared state, or data that multiple components may need

Why this matters:

  • URLs should remain meaningful and bookmarkable when possible
  • Complex objects usually should not be serialized into the URL
  • Routed components should be able to reload correctly if the page is refreshed
  • Some data is app state, not route state

A useful rule is:

  • If the data identifies what page to show, put it in the route
  • If the data changes how the page behaves, query params may fit
  • If the data is temporary or complex, use navigation state or a service

For modern Angular applications, the most practical choices are usually:

  1. Route params for IDs
  2. Query params for optional URL-visible values
  3. Shared service or state management for complex objects
  4. Navigation extras state for temporary non-URL data

For example, instead of passing a full property3 object, you often pass its id in the route and let SecondComponent load the full object again. That is usually the cleanest and most reliable approach.

Mental Model

Think of Angular routing like sending someone to a new office.

  • Route params are like writing the office number on the address label
  • Query params are like adding extra delivery notes
  • Navigation state is like handing the courier a private note that is not written on the envelope
  • Shared service is like storing a file in a shared cabinet that both offices can access

Now imagine sending a whole chair through the address label. That does not make sense. In the same way, large or complex objects usually do not belong in the URL.

So the mental model is:

  • Put small identifying information in the route
  • Put optional URL-visible settings in query params
  • Put rich shared data in a service or app state
  • Use navigation state only for temporary handoff data
Take Quiz

Syntax and Examples

1. Route parameters

Use route parameters when the destination component can load what it needs from an ID.

this.router.navigate(['/second', this.property1]);

Route configuration:

const routes: Routes = [
  { path: 'second/:id', component: SecondComponent }
];

Read it in the target component:

import { ActivatedRoute } from '@angular/router';

export class SecondComponent {
  constructor(private route: ActivatedRoute) {}

  ngOnInit() {
    const id = this.route.snapshot.paramMap.get('id');
    console.log(id);
  }
}

This is best when the route itself should identify the resource.

2. Query parameters

Use query parameters for optional values.

this.router.navigate(['/second'], {
  queryParams: {
    p1: this.property1,
    p2: this.property2
  }
});

Read them:

export class SecondComponent {
  constructor(private route: ActivatedRoute) {}

  ngOnInit() {
    const p1 = this.route.snapshot.queryParamMap.get('p1');
    const p2 = this.route.snapshot.queryParamMap.get('p2');
    console.log(p1, p2);
  }
}

This keeps values visible in the URL.

3. Navigation state

Use this for temporary data that should not appear in the URL.

this.router.navigate(['/second'], {
  state: {
    p1: this.property1,
    p2: this.property2,
    p3: this.property3
  }
});

Read it in the destination:

export class SecondComponent {
  data = history.state;

  ngOnInit() {
    console.log(this.data.p1);
    console.log(this.data.p2);
    console.log(this.data.p3);
  }
}

This is convenient, but remember: it is temporary and may not survive a full refresh the way route parameters do.

4. Shared service

Use a service when passing complex objects or shared state.

import { Injectable } from '@angular/core';

@Injectable({ providedIn: 'root' })
export class DataService {
  private data: TypeXY | null = null;

  setData(value: TypeXY) {
    this.data = value;
  }

  getData(): TypeXY | null {
    return this.data;
  }
}

Set the data before routing:

export class FirstComponent {
  constructor(private router: Router, private dataService: DataService) {}

  property3!: TypeXY;

  routeWithData() {
    this.dataService.setData(this.property3);
    this.router.navigate(['/second']);
  }
}

Read it in the destination:

export class SecondComponent {
  constructor(private dataService: DataService) {}

  ngOnInit() {
    const value = this.dataService.getData();
    console.log(value);
  }
}

This is simple and useful for complex data, but it depends on in-memory state.

Step by Step Execution

Consider this example using a route parameter and a service together.

// first.component.ts
routeWithData() {
  this.dataService.setData(this.property3);
  this.router.navigate(['/second', this.property1]);
}
// second.component.ts
ngOnInit() {
  const id = this.route.snapshot.paramMap.get('id');
  const item = this.dataService.getData();

  console.log('Route ID:', id);
  console.log('Shared object:', item);
}

Here is what happens step by step:

  1. The user clicks the button in FirstComponent.
  2. routeWithData() runs.
  3. The method stores property3 in DataService.
  4. The router navigates to /second/123 if property1 is 123.
  5. Angular destroys or deactivates FirstComponent.
  6. Angular creates SecondComponent for the new route.
  7. In ngOnInit(), SecondComponent reads the route param id.
  8. It also reads the shared object from DataService.
  9. The component now has both:
    • a URL-based identifier
    • a complex in-memory object

This mixed approach is common because it keeps the route meaningful while avoiding large objects in the URL.

Real World Use Cases

Passing an ID to load a record

A product list page routes to a product details page:

this.router.navigate(['/products', product.id]);

The details page uses the ID to request fresh data from an API.

Sending search filters in the URL

A reports page stores filters in query params:

this.router.navigate(['/reports'], {
  queryParams: { status: 'open', page: 2 }
});

This is useful because the URL can be bookmarked or shared.

Passing temporary form data during navigation

A multi-step form may use navigation state for data collected in the previous step.

this.router.navigate(['/checkout/review'], {
  state: { draftOrder: order }
});

Sharing selected objects between pages

An admin dashboard may store a selected object in a service before routing to an editor page.

This avoids putting a large object in the URL and keeps the code simple.

Preserving UI state across sibling routes

A shared service can hold current filters, sort order, selected tab, or wizard progress while users move through routed views.

Take Quiz

Real Codebase Usage

In real Angular projects, developers usually avoid passing full objects through the URL.

Common patterns include:

1. Pass only an ID, then fetch data

This is the most common and reliable pattern.

this.router.navigate(['/users', user.id]);

Then in the target component:

const id = this.route.snapshot.paramMap.get('id');
this.userService.getUserById(id!).subscribe();

Why teams prefer it:

  • URLs stay clean
  • Pages can reload correctly
  • Data comes from a single source of truth

2. Use guard clauses when route data is missing

When using service-based state or navigation state, the destination may not have data after refresh.

ngOnInit() {
  const data = this.dataService.getData();

  if (!data) {
    this.router.navigate(['/fallback']);
    return;
  }

  this.item = data;
}

This is a common guard-clause pattern.

3. Combine route params with shared state

Use route params for identity and a service for temporary richer data.

4. Use resolvers for preloading route data

In larger apps, a resolver may load data before the component appears. This is useful when the page depends on route params.

5. Use query params for page state

Filters, tab names, sort order, and pagination often belong in query params because they are user-visible and shareable.

6. Centralize shared state in services

Apps often use injectable services to keep selected entities, session context, or wizard progress available across routed components.

Common Mistakes

Using click instead of (click)

Broken:

<div class="button" click="routeWithData()">Pass data and route</div>

Correct Angular event binding:

<div class="button" (click)="routeWithData()">Pass data and route</div>

Trying to use @Input() with a routed component

Broken idea:

<app-second [p3]="property3"></app-second>

This renders SecondComponent inside the current template. It does not navigate through the router.

Use @Input() only for parent-child component relationships in templates.

Putting complex objects in the URL

Broken approach:

this.router.navigate(['/second', JSON.stringify(this.property3)]);

Problems:

  • URLs become messy
  • Serialization can fail or be unsafe
  • Refreshing and deep linking become harder

Prefer passing an ID or using a service.

Depending only on service state after refresh

If a service only stores data in memory, the data is lost when the page reloads.

const data = this.dataService.getData();
// data may be null after refresh

Avoid this by:

  • passing an ID in the route
  • reloading data from an API
  • storing longer-lived state elsewhere if needed

Using route params for optional UI settings

If data is optional, query params are often a better fit.

Use route params for required path identity, not every small option.

Reading params only once when you need updates

If the same component instance stays active while params change, snapshot may not be enough.

this.route.paramMap.subscribe(params => {
  console.log(params.get('id'));
});

Use subscription when the route values can change without recreating the component.

Comparisons

ApproachBest forVisible in URLWorks with complex objectsSurvives refresh wellTypical use
Route paramsRequired identifiersYesNoYes/users/42
Query paramsOptional settingsYesUsually noYes?page=2&sort=name
Navigation stateTemporary navigation dataNoYesNot reliablyPassing data during one navigation
Shared serviceIn-memory shared stateNoYesNo, unless persistedSelected item, wizard state
@Input()Nested component communicationNoYesN/AParent-child templates

Route params vs query params

  • Route params are part of the path and usually required
  • Query params are optional and often used for filtering or UI state

Navigation state vs shared service

  • Navigation state is good for a one-time handoff during routing
  • Shared service is better when multiple components need the same data or when you want a clearer state container

Shared service vs fetching by ID

  • Shared service is convenient for temporary in-memory objects
  • Fetching by ID is more robust for refreshes, deep links, and consistency

In production apps, fetching by ID from the route is often the strongest default choice.

Take Quiz

Cheat Sheet

Quick reference

Navigate with a route param

this.router.navigate(['/second', id]);

Read a route param

const id = this.route.snapshot.paramMap.get('id');

Navigate with query params

this.router.navigate(['/second'], {
  queryParams: { page: 1, filter: 'active' }
});

Read query params

const page = this.route.snapshot.queryParamMap.get('page');

Navigate with temporary state

this.router.navigate(['/second'], {
  state: { item: myObject }
});

Read navigation state

const item = history.state.item;

Share data through a service

this.dataService.setData(myObject);
this.router.navigate(['/second']);
const item = this.dataService.getData();

Rules of thumb

  • Use route params for IDs and required path data
  • Use query params for optional URL-visible values
  • Use navigation state for temporary data during one navigation
  • Use services for complex shared state
  • Do not use @Input() to pass data to a routed component
  • Do not put large objects in the URL

Edge cases

  • Service state is lost on full browser refresh unless you persist it
  • Navigation state is also temporary
  • If the destination must always work from a fresh URL, pass an ID and reload data

FAQ

How do I pass data to another component through Angular routing?

Use route params, query params, navigation state, or a shared service depending on the type of data. IDs usually go in the route, while complex objects usually go in a service or navigation state.

Can I pass an object through Angular route parameters?

Technically you can serialize data, but it is usually not a good idea. Complex objects do not belong in the URL in most cases. Pass an ID instead, or use a service.

Should I use @Input() for routed components?

No. @Input() is for components rendered directly in a parent template. Routed components are created by the router.

What is the best way to pass complex data between Angular routes?

A shared service is a common choice for complex objects. If the page must survive refreshes, pass an ID in the route and reload the object from a data source.

Is Angular router navigation state better than a service?

It depends. Navigation state is convenient for one-time temporary data during navigation. A service is better for broader shared state and reuse.

What happens to service-passed data if the page refreshes?

If the service stores data only in memory, the data is lost on refresh. That is why route IDs plus reloading are often more reliable.

When should I use query parameters instead of route parameters?

Use query parameters for optional values like filters, search, sorting, pagination, or tab state. Use route parameters for required path identity.

Take Quiz

Mini Project

Description

Build a small Angular flow where a user selects a product in one component and navigates to a details page. The route should pass the product ID in the URL, while a shared service temporarily holds the full selected object. This demonstrates the difference between route-friendly data and complex in-memory data.

Goal

Create a routed navigation flow that passes an ID through the URL and accesses a complex object through a shared Angular service.

Requirements

  • Create a ProductService that stores and returns a selected product object.
  • In the first component, save the selected product to the service before navigating.
  • Navigate to the second component using the product ID as a route parameter.
  • In the second component, read the route parameter and the stored product.
  • Add a fallback message if the service no longer has the product after refresh.
Take Quiz

Keep learning

Related questions

View more