19

I am developing an application using angular2. I have a scenario where I need to pass complex data (array of objects) from one component to another component(they are not parent-child, they are two separate components) while routing(using router.navigate()). I have googled this and most of the results describe the scenario of components that are parent-child. I have gone through the results and found these ways to pass the data.

1) Create Shared Service 2) Pass as route parameters

2nd approach works for me (even though, I don't like that when I have complex data as explained above). I am not able to share the data using shared service. So my questions is, does passing data between components using services only works when components are in parent-child relationship? Also, let me know if there are other prefered ways to pass data between one component and other?

Updated: I am adding bit of code from my scenario. Please let me know my mistake as to why passing data through shared services is not working.

I have 2 components: 1) SearchComponent 2) TransferComponent I am setting the data in SearchComponent and want to access the data in TransferComponent through utilityService.

Utility Service:

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

@Injectable()
export class UtilityService{
    constructor(){

    }
    public bioReagentObject = [];

    routeBioReagentObj(bioReagentObj){
        console.log("From UtilityService...", bioReagentObj);
        for (let each of bioReagentObj)
            this.bioReagentObject.push(each)
        // console.log("From UtilityService",this.bioReagentObject);
    }

    returnObject(){
        console.log("From UtilityService",this.bioReagentObject);
        return this.bioReagentObject
    }



}

searchcomponent.ts

    import {UtilityService} from "../services/utilityservice.component";
    import {Component, OnInit, OnDestroy, Input} from '@angular/core';

@Component({
    selector: 'bioshoppe-search-details',
    providers: [UtilityService],
    templateUrl: 'app/search/searchdetails.component.html',
    styleUrls: ['../../css/style.css']
})

export class SearchDetailsComponent implements OnInit, OnDestroy {
    constructor(private route: ActivatedRoute,
                private utilityService: UtilityService,
                private router: Router) {

    }
    @Input() selected: Array<String> = [{barcode:123, description:"xyz"}];

       //This method is called from .html and this.selected has the data to be     shared.
       toTransfer() {
        this.utilityService.routeBioReagentObj(this.selected);
        this.router.navigate(['/transfer']);

    }
}

TransferService.ts

import {Component, Input, OnInit, OnDestroy} from '@angular/core';
import {TransferService} from "../services/transferservice.component";
import {UserService} from "../services/userservice.component";
import {SearchService} from "../services/searchservice.component";
import {ActivatedRoute} from '@angular/router';
import {UtilityService} from "../services/utilityservice.component";


@Component({
    selector: 'bioshoppe-transfer',
    providers: [TransferService, UserService, SearchService, UtilityService],
    templateUrl: 'app/transfer/transfer.component.html',
    styleUrls: ['../../css/style.css', '../../css/transfer.component.css']
})

export class TransferComponent implements OnInit, OnDestroy{
    constructor(private transferService: TransferService,
                private userService: UserService,
                private searchService: SearchService,
                private utilityService: UtilityService,
                private route: ActivatedRoute
    ) {

    }
ngOnInit() {
// I am trying to access the data here, but it print "undefind"
console.log("From Transferpage",this.utilityService.returnObject());
}
}

I am sure something is wrong, but it's just that I am not able to figure it out.Any help is appreciated.

AngularChef
  • 13,797
  • 8
  • 53
  • 69
mrsan22
  • 727
  • 2
  • 11
  • 28
  • 1
    No, services do not need to have parent-child relation. Service can be created as Singleton for module and reused through the module. – laser Mar 02 '17 at 23:07
  • 1
    A shared service can be shared between any components they don't have to be parent/child relationship. For most cases a shared service should work, can you provide some example code of what you have and what you are trying to do. That way people can try give you examples – davidejones Mar 02 '17 at 23:08
  • you can have just DataService if you want. http://stackoverflow.com/questions/36835123/how-do-i-pass-data-in-angular-2-components-while-using-routing – laser Mar 02 '17 at 23:09
  • @davidejones I have added code to make my scenario more clear. Please let me know, if you find out any issue with it. – mrsan22 Mar 03 '17 at 02:28
  • @laser I thought so, but most of the examples that I looked where giving examples, where the components wherein a parent-child relationship. I have added some example code. Please let me know your input. – mrsan22 Mar 03 '17 at 02:37
  • 1
    If you want to share a service and hold data on it, just be sure that this service is a provider of a module that is on top of all the other modules. For example, add your `UtilityService` to the `providers:` of your `app.module.ts`. If you add a provider to a `@Component()` you will get a brand new instance of that service, in this case, your `UtilityService`. – dlcardozo Mar 03 '17 at 02:41

3 Answers3

25

camaron is right. Your mistake is that you declare UtilityService twice:

  1. Once in the providers of SearchComponent.
  2. Once in the providers of TransferComponent.

You should declare the service ONLY ONCE to make sure both components get the same instance. For this you can choose between either of these options:

  1. Declare the service in the providers of a @NgModule which has both SearchComponent and TransferComponent in its declarations. 9 times out of 10 this is the right solution!
  2. Declare the service in the providers of a @Component which is a parent of both SearchComponent and TransferComponent. This might not be feasible depending how your component tree looks.

Following option #1, you end up with:

@NgModule({
  imports: [CommonModule, ...],

  // Look, the components injecting the service are here:
  declarations: [SearchComponent, TransferComponent, ...],

  // Look, the service is declared here ONLY ONCE:
  providers: [UtilityService, ...]
})
export class SomeModule { }

Then inject UtilityService in your components' constructors WITHOUT REDECLARING IT in the components's providers:

@Component({
  selector: 'foo',
  template: '...',
  providers: []  // DO NOT REDECLARE the service here
})
export class SearchComponent {
  constructor(private utilityService: UtilityService) { }
}

@Component({
  selector: 'bar',
  template: '...',
  providers: []  // DO NOT REDECLARE the service here
})
export class TransferComponent {
  constructor(private utilityService: UtilityService) { }
}
AngularChef
  • 13,797
  • 8
  • 53
  • 69
  • not putting the service into the component's providers array causes this error: Error: Uncaught (in promise): Error: No provider for [servicename] – GregJF Sep 21 '17 at 05:24
  • It shouldn't. Not if you declare said service in `@NgModule.providers` elsewhere in your app. Note that the NgModule in which you declare the service should NOT be lazy-loaded. – AngularChef Sep 21 '17 at 10:16
  • I have a similar set up as the original post. When my other components call returnObject() I get errors because the http call has not yet finished and the variable returned in returnObject is not yet set. How could I get around this? – Anthony Sep 25 '17 at 21:38
  • 1
    @Anthony: You obtain the data from an HTTP call by `.subscribing` to its observable. You're probably trying to subscribe in the service making the call instead of subscribing in the component consuming the service. Your service should **return the observable** wrapping the HTTP call (**without subscribing to it**) and then the different parts of your app interested in the data can subscribe to that observable. – AngularChef Sep 26 '17 at 07:38
  • @AngularFrance I think im following you but not sure how to subscribe to the observable multiple times. Please check out the question I just asked here: https://stackoverflow.com/questions/46429698/angular-share-http-data-subscribe-to-same-observable-multiple-times – Anthony Sep 26 '17 at 14:51
  • Somehow we are not able to get the message on the target component. I am using angular 5.2. In my case, we are using the multiple module approach. Now here both modules are different and independent. But they have one module in common on the top. Please suggest. – Karan Jul 24 '18 at 11:06
  • @AngularChef 'declare the service ONLY ONCE' this statement saved my time. Thanks – Sujatha Girijala Jan 30 '19 at 09:21
4

You need to distinguish when service is from NgModule, or when it comes from other Component.

Basics

In Angular2 you have a hierarchical tree of components with parent-child relation (as you noticed). At the same time all services are injected with help of injectors, which also form a hierarchy tree (in parallel to component hierarchy). The tree of injectors do not necessary coincide with component tree, as there could be proxy injectors from parent for efficiency purposes.

How it works

The role of the injectors is to take the service you define in constructor and inject it into your component, i.e. find it, initiate if not found and provide it to you. When you put into constructor that you want some service, it will go look into injector tree from your component up to the root (which is NgModule) for service existence. If injector finds that there is a defined provider for this service but no service instance, it will create the service. If no provider it continues to go up.

What you have seen so far

What you have seen is that the parent component defines through "providers" the service, and injectors in search for the service goes one hop up in the tree, finds the provider, initiate service and gives to your child component. In order for all components of some subtree to have the same Service, you need to define provider only in the common root of the subtree. If you want to have same service for whole module, you need define this service in providers of module. This is so-called Singleton service.

Some extra

Normally if you want to inject service and could not find it all the way up to root, you will get error during compilation/runtime. But if you define that you want to inject Optional service, injector will not fail if the provider for this service is not found.

More or less...

Some useful links:

  1. https://angular.io/docs/ts/latest/guide/hierarchical-dependency-injection.html

  2. https://blog.thoughtram.io/angular/2015/05/18/dependency-injection-in-angular-2.html

laser
  • 570
  • 4
  • 20
  • I find your answer appealing and complete, however, it is mssing some badly needed code snippets to juice up the value of your answer. (e.g http://stackoverflow.com/a/40033975/3991654, http://stackoverflow.com/a/40191287/3991654) -- Respenctfully – Aaron C Mar 29 '17 at 15:23
1

You could achieve through sharing data between two components using Observables.

I didn't write this post but its quite useful and easy to understand -

A quick post to show an example of something that got me stuck for a little while - how to communicate between components in Angular 2/4.

The solution is to use an Observable and a Subject (which is a type of observable)

http://jasonwatmore.com/post/2016/12/01/angular-2-communicating-between-components-with-observable-subject

Aamol
  • 1,149
  • 1
  • 15
  • 23