Search code examples
javascriptangularangular-servicesangular-router

Pass input to Angular components via router


In Angular 2, I have my routes defined in app.module.ts:

const appRoutes: Routes = [
  { 
    path: '',
    component: HomeComponent,
  },
  {
    path: 'admin',
    component: AdminComponent,
  }
];

I also have an app.component which displays the menu and a search form. This app.component is connected to a service (events.service.ts) which returns an array of events. When the search form is submitted, app.component calls the service to filter the events and then grabs them:

getEvents(): void {
    this.eventsService.getEvents().then(events => {this.events = events});
}

onSubmit() {
    this.searchTerm = this.model.searchTerm;
    this.eventsService.search(this.searchTerm).then(res => this.getEvents());
}

I would like to be able to pass this.events from the app.component down to the two routes specified in app.module (home and admin).

My home.component.ts requires the same events.service.ts, and grabs events from it in an onNgInit function, but when the events in the service have been updated via the search in app.component.ts, the events grabbed in the initialisation of home.component.ts are out of date. I would like them to be synchronised.


Solution

  • You cannot directly pass data as "input" to the route. Components which are placed "at the other side of" <router-outlet> are not visible in template, hence the notion of an "input" is not applicable in such cases.

    The idea of router is to encode as much of application state as possible in the route. Of course, you can implement some sort of caching to avoid multiple requests for the same resource that you expect to stay the same during reasonable amount of time.

    Of course, not all data can be transferred through route. For example, you usually write only some sort of identification for an entity, and then use HTTP to bring more data down based on the ID read from the route.

    That said, you can keep your data in a service. From your component, save some data in a service instance which you've injected. Because services are singletons, when you inject the same service in a component which is used as a child route, you'll have the data placed in it from the parent route.

    // componentA
    this.service.data = data; // from http request, for example
    
    // componentB
    this.data = this.service.data;
    

    Of course, depending on the timing of the operations above, it might happen that code in component B executes before code in component A, which means that you would grab undefined from Service#data. This is especially a common case if data is fetched asynchronously, which is often the case with HTTP requests.

    For this, you can use observables, probably a BehvaiorSubject in order to grab data whenever you subscribe.

    // service
    data$ = new BehaviorSubject<T>(null) // initial value as arg
    
    // component A
    this.service.data$.next(data) // from http request, for example
    
    // component B
    this.service.data$.subscribe(data => this.data = data)
    

    Now your data in component B will update as soon as data is fetched in component A, though the data$ observable in the singleton service injected in both components.

    Be sure to unsubscribe as well, although you can use async pipe in Angular too. In that case, Angular will handle the unsubscription for you and in most cases this is the preferred way of using observables in Angular.