Photo by Matt Artz on Unsplash

How to pass data between routed components in Angular

In this article we will show how two unrelated components in Angular can pass some data around.

There are several ways how Angular components can pass data around:

  • Using @Input and @Output
  • By injecting parent component through constructor or child components through @ViewChild, @ViewChildren, @ContentChild, @ContentChildren and directly calling component’s API
  • Using services (this covers state management libraries like ngrx)
  • Using router parameters

First two methods can only be used when we have parent — child relationship between our components — one of the components is the parent node in the DOM:

When we have routed components, then it looks like this:

There is no parent child relationship between components A and B, so we cannot use first two methods to pass the data. We could use route parameters and pass some identifier to the routed component and the data could be resolved through router but imagine that we don’t have an id and we want to pass rather big object to other component. We also don’t want to have all the object properties in query parameters. How can Component A pass some data to Component B? We can solve it by using service:

export class ComponentA {
   constructor(private stateService: StateService) {}
   goToComponentB(): void {
this.stateService.data = {...};
this.router.navigate(['/b']);
}
}
export class ComponentB implements OnInit {
    constructor(private stateService: StateService) {}
    ngOnInit(): void {
this.data = this.stateService.data;
this.stateService.data = undefined;
}
}

Component A stores the data just right before navigation on the StateService. Component B then reads the data in OnInit lifecycle hook and clears the data in the StateSevice. This solution has few drawbacks:

  • no possibility to use RouterLink directive in the template
  • both components have new dependency — StateService
  • need to clear shared data manually after navigation — in this case Component B is responsible for that

New router state attribute

Angular 7.2.0 introduced new way of passing the data when navigating between routed components. In NavigationExtras interface within router module was added a new attribute: state: any and you can set the data which will be present in the new state. So goToComponentB function will look like this:

export class ComponentA {
    constructor(private router: Router) {}
    goToComponentB(): void {
this.router.navigate(['/b'], {state: {data: {...}}});
}
}

Or we can get rid of the router dependency too by using RouterLink directive:

<a [routerLink]=”/b” [state]=”{ data: {...}}”>Go to B</a>

Getting the data

Getting the data is not so obvious at a first glance. Someone could expect that ActivatedRoute will contain it, but there is no attribute for state. The state property was added to Navigation which is available through Router.getCurrentNavigation().extras.state. Problem is that getCurrentNavigation returns Navigation only during the navigation and returns null after the navigation ended. So the Navigationis no longer available in Component’s B onInit lifecycle hook. We need to read the data from browser’s history object:

history.state.data

Testing

It is quite easy to provide testing state value, just use history.pushState() function:

history.pushState({data: {...}}, '', '');

Note about navigationId

Angular modifies object passed as a state by adding navigationId attribute so be careful about that. If we would navigate with

this.router.navigate([‘/b’], {state: {...}})

then history.state would contain:

{... dataObjectAttributes, navigationId: 123}

When sending {state: {data: {...}}}, there is {data: {...}, navigationId: 123} so when we get data from history.state.datawe are safe.

Written by Ľudovít Hajzer and Martin Nepsinsky.