[angular] How do I pass data to Angular routed components?

In one of my Angular 2 routes's templates (FirstComponent) I have a button

first.component.html

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

My goal is to achieve:

Button click -> route to another component while preserving data and without using the other component as a directive.

This is what I tried...

1ST APPROACH

In the same view I am storing collecting same data based on user interaction.

first.component.ts

export class FirstComponent {
     constructor(private _router: Router) { }

     property1: number;
     property2: string;
     property3: TypeXY; // this a class, not a primitive type

    // here some class methods set the properties above

    // DOM events
    routeWithData(){
         // here route
    }
}

Normally I'd route to SecondComponent by

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

eventually passing the data by

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

whereas the definition of the link with parameters would be

@RouteConfig([
      // ...
      { path: '/SecondComponent/:p1:p2', name: 'SecondComponent', component: SecondComponent} 
)]

The issue with this approach is that I guess I can't pass complex data (e.g. an object like property3) in-url;

2ND APPROACH

An alternative would be including SecondComponent as directive in FirstComponent.

  <SecondComponent [p3]="property3"></SecondComponent>

However I want to route to that component, not include it!

3RD APPROACH

The most viable solution I see here would be to use a Service (e.g. FirstComponentService) to

  • store the data (_firstComponentService.storeData()) on routeWithData() in FirstComponent
  • retrieve the data (_firstComponentService.retrieveData()) in ngOnInit() in SecondComponent

While this approach seems perfectly viable, I wonder whether this is the easiest / most elegant way to achieve the goal.

In general I'd like to know whether I'm missing other potential approaches to pass the data between components, particularly with the less possible amount of code

This question is related to angular typescript

The answer is


Angular 7.2.0 introduced new way of passing the data when navigating between routed components:

@Component({
  template: `<a (click)="navigateWithState()">Go</a>`,
})
export class AppComponent  {
  constructor(public router: Router) {}
  navigateWithState() {
    this.router.navigateByUrl('/123', { state: { hello: 'world' } });
  }
}

Or:

@Component({
  selector: 'my-app',
  template: `
  <a routerLink="/details" [state]="{ hello: 'world' }">Go</a>`,
})
export class AppComponent  {}

To read the state, you can access window.history.state property after the navigation has finished:

export class PageComponent implements OnInit {
  state$: Observable<object>;

  constructor(public activatedRoute: ActivatedRoute) {}

  ngOnInit() {
    this.state$ = this.activatedRoute.paramMap
      .pipe(map(() => window.history.state))
  }
}

use a shared service to store data with a custom index. then send that custom index with queryParam. this approach is more flexible.

// component-a : typeScript :
constructor( private DataCollector: DataCollectorService ) {}

ngOnInit() {
    this.DataCollector['someDataIndex'] = data;
}

// component-a : html :
<a routerLink="/target-page" 
   [queryParams]="{index: 'someDataIndex'}"></a>

.

// component-b : typeScript :
public data;

constructor( private DataCollector: DataCollectorService ) {}

ngOnInit() {
    this.route.queryParams.subscribe(
        (queryParams: Params) => {
            this.data = this.DataCollector[queryParams['index']];
        }
    );
}

Pass using JSON

  <a routerLink = "/link"
   [queryParams] = "{parameterName: objectToPass| json }">
         sample Link                   
  </a>

I this the other approach not good for this issue. I thing the best approach is Query-Parameter by Router angular that have 2 way:

Passing query parameter directly

With this code you can navigate to url by params in your html code:

<a [routerLink]="['customer-service']" [queryParams]="{ serviceId: 99 }"></a>

Passing query parameter by Router

You have to inject the router within your constructor like:

constructor(private router:Router){

}

Now use of that like:

goToPage(pageNum) {
    this.router.navigate(['/product-list'], { queryParams: { serviceId: serviceId} });
}

Now if you want to read from Router in another Component you have to use of ActivatedRoute like:

constructor(private activateRouter:ActivatedRouter){

}

and subscribe that:

  ngOnInit() {
    this.sub = this.route
      .queryParams
      .subscribe(params => {
        // Defaults to 0 if no query param provided.
        this.page = +params['serviceId'] || 0;
      });
  }

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

well the easiest way to do it in angular 6 or other versions I hope is to simply to define your path with the amount of data you want to pass

{path: 'detailView/:id', component: DetailedViewComponent}

as you can see from my routes definition, I have added the /:id to stand to the data I want to pass to the component via router navigation. Therefore your code will look like

<a class="btn btn-white-view" [routerLink]="[ '/detailView',list.id]">view</a>

in order to read the id on the component, just import ActivatedRoute like

import { ActivatedRoute } from '@angular/router'

and on the ngOnInit is where you retrieve the data

ngOnInit() {
       this.sub = this.route.params.subscribe(params => {
        this.id = params['id'];
        });
        console.log(this.id);
      }

you can read more in this article https://www.tektutorialshub.com/angular-passing-parameters-to-route/


I think since we don't have $rootScope kind of thing in angular 2 as in angular 1.x. We can use angular 2 shared service/class while in ngOnDestroy pass data to service and after routing take the data from the service in ngOnInit function:

Here I am using DataService to share hero object:

import { Hero } from './hero';
export class DataService {
  public hero: Hero;
}

Pass object from first page component:

 ngOnDestroy() {
    this.dataService.hero = this.hero; 
 }

Take object from second page component:

 ngOnInit() {
    this.hero = this.dataService.hero; 
 }

Here is an example: plunker


3rd approach is most common way to share data between components. you may inject the item service which you want to use in related component.

import { Injectable } from '@angular/core';
import { Predicate } from '../interfaces'

import * as _ from 'lodash';

@Injectable()
export class ItemsService {

    constructor() { }


    removeItemFromArray<T>(array: Array<T>, item: any) {
        _.remove(array, function (current) {
            //console.log(current);
            return JSON.stringify(current) === JSON.stringify(item);
        });
    }

    removeItems<T>(array: Array<T>, predicate: Predicate<T>) {
        _.remove(array, predicate);
    }

    setItem<T>(array: Array<T>, predicate: Predicate<T>, item: T) {
        var _oldItem = _.find(array, predicate);
        if(_oldItem){
            var index = _.indexOf(array, _oldItem);
            array.splice(index, 1, item);
        } else {
            array.push(item);
        }
    }


    addItemToStart<T>(array: Array<T>, item: any) {
        array.splice(0, 0, item);
    }


    getPropertyValues<T, R>(array: Array<T>, property : string) : R
    {
        var result = _.map(array, property);
        return <R><any>result;
    }

    getSerialized<T>(arg: any): T {
        return <T>JSON.parse(JSON.stringify(arg));
    }
}



export interface Predicate<T> {
    (item: T): boolean
}

say you have

  1. component1.ts
  2. component1.html

and you want to pass data to component2.ts.

  • in component1.ts is a variable with data say

      //component1.ts
      item={name:"Nelson", bankAccount:"1 million dollars"}
    
      //component1.html
       //the line routerLink="/meter-readings/{{item.meterReadingId}}" has nothing to 
      //do with this , replace that with the url you are navigating to
      <a
        mat-button
        [queryParams]="{ params: item | json}"
        routerLink="/meter-readings/{{item.meterReadingId}}"
        routerLinkActive="router-link-active">
        View
      </a>
    
      //component2.ts
      import { ActivatedRoute} from "@angular/router";
      import 'rxjs/add/operator/filter';
    
      /*class name etc and class boiler plate */
      data:any //will hold our final object that we passed 
      constructor(
      private route: ActivatedRoute,
      ) {}
    
     ngOnInit() {
    
     this.route.queryParams
      .filter(params => params.reading)
      .subscribe(params => {
      console.log(params); // DATA WILL BE A JSON STRING- WE PARSE TO GET BACK OUR 
                           //OBJECT
    
      this.data = JSON.parse(params.item) ;
    
      console.log(this.data,'PASSED DATA'); //Gives {name:"Nelson", bankAccount:"1 
                                            //million dollars"}
       });
      }
    

One fine solution is to implement a Guard with canActivate method. In this scenario you can fetch data from a given api and let user access the component describe in the routing file. In the meantime one can set the data property of the route object and retrieve it in the component.

Let say you have this routing conf:

const routes: Routes = [
    { path: "/:projectName", component: ProjectComponent, canActivate: [ProjectGuard] }
]`

in your guard file you may have:

canActivate(next: ActivatedRouteSnapshot,state: RouterStateSnapshot)
: Observable<boolean> | Promise<boolean> | boolean {
return this.myProjectService.getProject(projectNameFoundElsewhere).pipe(
  map((project) => {
    if (project) {
      next.data = project;
    }
    return !!project;
  }),
);

}`

Then in your component

constructor(private route: ActivatedRoute) {
    this.route.data.subscribe((value) => (this.project = value));
}

This way is a bit different than passing via a service since service keep the value in a behaviorSubject as long as it is not unset. Passing via tha guard make the data available for the current route. I havent check if the children routes keep the data or not.


Some super smart person (tmburnell) that is not me suggests re-writing the route data:

let route = this.router.config.find(r => r.path === '/path');
route.data = { entity: 'entity' };
this.router.navigateByUrl('/path');

As seen here in the comments.

I hope someone will find this useful


I looked at every solution (and tried a few) from this page but I was not convinced that we have to kind of implement a hack-ish way to achieve the data transfer between route.

Another problem with simple history.state is that if you are passing an instance of a particular class in the state object, it will not be the instance while receiving it. But it will be a plain simple JavaScript object.

So in my Angular v10 (Ionic v5) application, I did this-

this.router.navigateByUrl('/authenticate/username', {
    state: {user: new User(), foo: 'bar'}
});

enter image description here

And in the navigating component ('/authenticate/username'), in ngOnInit() method, I printed the data with this.router.getCurrentNavigation().extras.state-

ngOnInit() {
    console.log('>>authenticate-username:41:',
        this.router.getCurrentNavigation().extras.state);
}

enter image description here

And I got the desired data which was passed-

enter image description here


By default i won't use a guard for this one for me it is more can i enter the route or can i leave it. It is not to share data betweenn them.

If you want to load data before we entered a route just add an resolver to this one this is also part of the Router.

As very basic example:

Resolver

import { Resolve, ActivatedRoute } from "@angular/router";
import { Observable } from "rxjs";
import { Injectable } from "@angular/core";
import { take } from "rxjs/operators";

@Injectable()
export class UserResolver implements Resolve<User> {

    constructor(
        private userService: UserService,
        private route: ActivatedRoute
    ) {}

    resolve(): Observable<firebase.User> {
        return this.route.params.pipe(
            switchMap((params) => this.userService.fetchUser(params.user_id)),
            take(1)
        );
    }
}

put to the router:

RouterModule.forChild([
{
    path: "user/:user_id",
    component: MyUserDetailPage,
    resolve: {
        user: UserResolver
    }
  }
}]

get the data in our component

ngOnInit() {
    const user: firebase.User = this.activatedRoute.snapshot.data.user;
}

The downside on this approach is, he will enter the route first if he get the user data not before, this ensures the data for the user has been loaded and is ready on start of the component, but you will stay on the old page as long the data has been loaded (Loading Animation)


You can use BehaviorSubject for sharing data between routed components. A BehaviorSubject holds one value. When it is subscribed it emits the value immediately. A Subject doesn't hold a value.

In the service.

@Injectable({
  providedIn: 'root'
})
export class CustomerReportService extends BaseService {
  reportFilter = new BehaviorSubject<ReportFilterVM>(null);
  constructor(private httpClient: HttpClient) { super(); }

  getCustomerBalanceDetails(reportFilter: ReportFilterVM): Observable<Array<CustomerBalanceDetailVM>> {
    return this.httpClient.post<Array<CustomerBalanceDetailVM>>(this.apiBaseURL + 'CustomerReport/CustomerBalanceDetail', reportFilter);
  }
}

In the component you can subscribe to this BehaviorSubject.

this.reportService.reportFilter.subscribe(f => {
      if (f) {
        this.reportFilter = f;
      }
    });

Note: Subject won't work here, Need to use Behavior Subject only.


It is 2019 and many of the answers here would work, depending on what you want to do. If you want to pass in some internal state not visible in URL (params, query) you can use state since 7.2 (as I have learned just today :) ).

From the blog (credits Tomasz Kula) - you navigate to route....

...from ts: this.router.navigateByUrl('/details', { state: { hello: 'world' } });

...from HTML template: <a routerLink="/details" [state]="{ hello: 'world' }">Go</a>

And to pick it up in the target component:

constructor(public activatedRoute: ActivatedRoute) {}

  ngOnInit() {
    this.state$ = this.activatedRoute.paramMap
      .pipe(map(() => window.history.state))
  }

Late, but hope this helps someone with recent Angular.


Solution with ActiveRoute (if you want pass object by route - use JSON.stringfy/JSON.parse):

Prepare object before sending:

export class AdminUserListComponent {

  users : User[];

  constructor( private router : Router) { }

  modifyUser(i) {

    let navigationExtras: NavigationExtras = {
      queryParams: {
          "user": JSON.stringify(this.users[i])
      }
    };

    this.router.navigate(["admin/user/edit"],  navigationExtras);
  }

}

Receive your object in destination component:

export class AdminUserEditComponent  {

  userWithRole: UserWithRole;      

  constructor( private route: ActivatedRoute) {}

  ngOnInit(): void {
    super.ngOnInit();

      this.route.queryParams.subscribe(params => {
        this.userWithRole.user = JSON.parse(params["user"]);
      });
  }

}

Examples related to angular

error NG6002: Appears in the NgModule.imports of AppModule, but could not be resolved to an NgModule class error TS1086: An accessor cannot be declared in an ambient context in Angular 9 TS1086: An accessor cannot be declared in ambient context @angular/material/index.d.ts' is not a module Why powershell does not run Angular commands? error: This is probably not a problem with npm. There is likely additional logging output above Angular @ViewChild() error: Expected 2 arguments, but got 1 Schema validation failed with the following errors: Data path ".builders['app-shell']" should have required property 'class' Access blocked by CORS policy: Response to preflight request doesn't pass access control check origin 'http://localhost:4200' has been blocked by CORS policy in Angular7

Examples related to typescript

TS1086: An accessor cannot be declared in ambient context Element implicitly has an 'any' type because expression of type 'string' can't be used to index Angular @ViewChild() error: Expected 2 arguments, but got 1 Typescript: No index signature with a parameter of type 'string' was found on type '{ "A": string; } Understanding esModuleInterop in tsconfig file How can I solve the error 'TS2532: Object is possibly 'undefined'? Typescript: Type 'string | undefined' is not assignable to type 'string' Typescript: Type X is missing the following properties from type Y length, pop, push, concat, and 26 more. [2740] Can't perform a React state update on an unmounted component TypeScript and React - children type?