How to share service between two modules - @NgModule in angular not between to components?
Asked Answered
C

7

49

In my application, I have two different bootstrap module (@NgModule) running independently in one application. There are not one angular app bit separate bootstrap module and now I want they should communicate to each other and share data.

I know through @Injectable service as the provider in the module, I can share data in all component under @NgModule but how I will share data between two different module ( not component inside module ).

Is there a way one service object can be accessed in another module? Is there a way I can access the object of Service available in browser memory and use it in my other angular module?

Chasechaser answered 17/10, 2016 at 14:40 Comment(8)
What do you mean by module? An @NgModule(). Services provided in @NgModule() are shared with the whole application and therefore also with all modules in your application (except when the module where you provide the service is lazy loaded).Innocuous
I mean two different anglar2 module. Yes I know service can share data all across my module. I want to share data out side my module and in another module created by other teamChasechaser
If you've provided a service in an @NgModule, all non-lazy-loaded modules below may use that service. If you lazy-load, each lazy-loaded module must provide those services. If you need to use it in a different application, either offer an API endpoint or send them the code.Georganngeorge
What do you mean with "Angular2 module" and what do you mean by "running independently". It's still not clear to me. Do you mean two Angular2 applications where two different modules are bootstrapped?Innocuous
@GünterZöchbauer angula2 module means the angular2 module created with NgModule decorator. My team is creating one angular2 module and another team is creating another module which will be in same html page. now they want to share same data. like if i access set some data, they can be available in other moduleChasechaser
How are these different modules loaded into the page? If you just import other modules they are part of the same application and services are just shared by default.Innocuous
The two different module will be loaded differently and don't have any relation. that is way i want to access it from the browser memory. As angular 2 make any typescript class @Injectable. is there a way I can get that service from available object in JavaScript.Chasechaser
Possible duplicate of What is the best way to share services across Modules in angular2Macri
S
11

2021-02-03

In recent Angular versions this is solved using @Injectable({providedIn: 'platform'})

https://angular.io/api/core/Injectable

Original

You can instantiate a service outside Angular and provide a value:

class SharedService {
  ...
}
window.sharedService = new SharedService();

@NgModule({
  providers: [{provide: SharedService, useValue: window.sharedService}],
  ...
})
class AppModule1 {}

@NgModule({
  providers: [{provide: SharedService, useValue: window.sharedService}],
  ...
})
class AppModule2 {}

If one application change the state in SharedService or calls a method that causes an Observable to emit a value and the subscriber is in a different application than the emitter, the code in the subscriber is executed in the NgZone of the emitter.

Therefore when subscribing to an observable in SharedService use

class MyComponent {
  constructor(private zone:NgZone, private sharedService:SharedService) {
    sharedService.someObservable.subscribe(data => this.zone.run(() => {
      // event handler code here
    }));
  }
}

See also How to dynamically create bootstrap modals as Angular2 components?

Sickler answered 17/10, 2016 at 15:1 Comment(11)
@Gunter, I am getting Duplicate identifier error doing the same thing as you've done.Diabetes
Sorry, but that is not enough information. Can you provide a Plunker? I'd suggest you create a new question that shows your code, a Plunker, and the full error message.Innocuous
Well, actually can you elaborate on what the part private sharedService.subscribe ( ... ) does? The private access specifier seems to be included twice, i.e. once in the constructor params, then within the constructor definition.Diabetes
Thanks for the hint. The one private was redundant - fixed. The other one is also redundant if sharedService and zone is only used to subscribe in the constructor. There also needs to be an observable in the service (I added someObservable).Innocuous
@GünterZöchbauer, I managed to bring a communication logic between two separately bootstrapped app modules using a Subject (which I suppose inherits from Observable class) instance in the service without ever calling the zone.run(). Can you tell me if I still need the ngZone here?Diabetes
I think so, because otherwise change detection won't run. Not every situation requires change detection. In the general case you probably do though.Innocuous
Also, isn't there a syntax error in doing ... .subscribe(data => this.zone.run() => { // event handler code here }), as I don't understand how it works?Diabetes
You are right, I forgot ( and ) - fixed. Thanks for the hint. If you bootstrap two components from the same AppModule, then you also don't need zone.run(), only if you want to communicate between two applications that are bootstrapped individually. In some cases ChangeDetectorRef.markForCheck(), ChangeDetectorRef.detectChanges(), ApplicationRef.tick(), setTimeout(...), or | async will have a similar effect and invoke change detection. zone.run() is really just the most generic case.Innocuous
This could work but it's not an elegant solution. the right way of doing this is implementing a static method forRoot() and returning a unique instance of your services. Have a look here: https://mcmap.net/q/356446/-what-is-the-best-way-to-share-services-across-modules-in-angular2Ries
@Ries you are right. I didn't undetstand modules properly back then and mixed it up with similar questions about sharing between individual Angular applications which were asked frequently earlier.Innocuous
providedIn: 'platform' not fixed the problem. Could you please have a look at #66138469Heterogony
P
58

As per the final version of Angular 2, services provided by a module are available to every other module that imports it. The Official Style Guide advice that application-wide services (singletons) that are to be reused anywhere in the application should be provided by some Core Module, that is to be imported in the main App Module so it would be injectable everywhere.

If you do not use a structure that involves a Core Module with shared singletons, and you are independently developing two NgModules, and you want a service in one of them to be used in the other, then the only solution is to import the provider into the other :

Here's the provider module:

/// some.module.ts
import { NgModule } from '@angular/core';

import { SomeComponent }   from './some.component';

@NgModule({
    imports: [],
    exports: [],
    declarations: [SomeComponent],
    providers: [ MyService ], // <======================= PROVIDE THE SERVICE
})
export class SomeModule { }

Here's the other module, that wants to use MyService

/// some-other.module.ts
import { NgModule } from '@angular/core';

import { SomeModule } from 'path/to/some.module'; // <=== IMPORT THE JSMODULE

import { SomeOtherComponent }   from './some.other.component';

@NgModule({
    imports: [ SomeModule ], // <======================== IMPORT THE NG MODULE
    exports: [],
    declarations: [SomeOtherComponent],
    providers: [],
})
export class SomeOtherModule { }

This way, the service should be injectable in any component SomeOtherModule declares, and in SomeModule itself - just ask for it in the constructor:

/// some-other.module.ts

import { MyService } from 'path/to/some.module/my-service';

/* ...
    rest of the module
*/

export class SomeOtherModule {
    constructor( private _myService: MyService) { <====== INJECT THE SERVICE
        this._myService.dosmth();
    }
}

If this doesn't answer your question, I invite you to re-formulate it.

Powerdive answered 13/11, 2016 at 17:27 Comment(7)
I have tried this line from your code: import { MyService } from 'path/to/some.module/my-service'; But I am getting an error stating that 'path/to/some.module/my-service' is not foundGwenore
@Gwenore this generally means that your transpiler can't find your file at that path. Check that your service file's path (relative to your project source) and your import statement match, (you don't need the extension in the import path). But this is a compilation/transpilation issue, not Angular stuff, so I won't further this problem here.Powerdive
Does this answer really work for separately bootstrapped modules? I tried, but it does not work for me. It works only if the shared service is used in the components within a module.Caressa
this doesn't work. It creates two instances of each service, so you're not really sharing a service... Have a look here: https://mcmap.net/q/356446/-what-is-the-best-way-to-share-services-across-modules-in-angular2Ries
Do you have an example of this 'CoreModule' that has services shared across various modules in the app?Country
This doesn't work. LeonardoX has a working reference! Worked for me https://mcmap.net/q/356446/-what-is-the-best-way-to-share-services-across-modules-in-angular2Balboa
Could you please have a look at #66138469Heterogony
F
22
  1. create shared module

    @NgModule({})
    export class SharedModule {
        static forRoot(): ModuleWithProviders {
            return {
                ngModule: SharedModule,
                providers: [SingletonService]
            };
        }
    }
    
  2. in the app module are your parent app module import the shared module like that

    SharedModule.forRoot()
    
  3. any the children modules if you need to import the shared module import it without the for root

    SharedModule
    

https://angular-2-training-book.rangle.io/handout/modules/shared-modules-di.html

Fireproofing answered 15/1, 2018 at 11:30 Comment(1)
One of the best answer. Ionic does the same thing.Avelin
S
11

2021-02-03

In recent Angular versions this is solved using @Injectable({providedIn: 'platform'})

https://angular.io/api/core/Injectable

Original

You can instantiate a service outside Angular and provide a value:

class SharedService {
  ...
}
window.sharedService = new SharedService();

@NgModule({
  providers: [{provide: SharedService, useValue: window.sharedService}],
  ...
})
class AppModule1 {}

@NgModule({
  providers: [{provide: SharedService, useValue: window.sharedService}],
  ...
})
class AppModule2 {}

If one application change the state in SharedService or calls a method that causes an Observable to emit a value and the subscriber is in a different application than the emitter, the code in the subscriber is executed in the NgZone of the emitter.

Therefore when subscribing to an observable in SharedService use

class MyComponent {
  constructor(private zone:NgZone, private sharedService:SharedService) {
    sharedService.someObservable.subscribe(data => this.zone.run(() => {
      // event handler code here
    }));
  }
}

See also How to dynamically create bootstrap modals as Angular2 components?

Sickler answered 17/10, 2016 at 15:1 Comment(11)
@Gunter, I am getting Duplicate identifier error doing the same thing as you've done.Diabetes
Sorry, but that is not enough information. Can you provide a Plunker? I'd suggest you create a new question that shows your code, a Plunker, and the full error message.Innocuous
Well, actually can you elaborate on what the part private sharedService.subscribe ( ... ) does? The private access specifier seems to be included twice, i.e. once in the constructor params, then within the constructor definition.Diabetes
Thanks for the hint. The one private was redundant - fixed. The other one is also redundant if sharedService and zone is only used to subscribe in the constructor. There also needs to be an observable in the service (I added someObservable).Innocuous
@GünterZöchbauer, I managed to bring a communication logic between two separately bootstrapped app modules using a Subject (which I suppose inherits from Observable class) instance in the service without ever calling the zone.run(). Can you tell me if I still need the ngZone here?Diabetes
I think so, because otherwise change detection won't run. Not every situation requires change detection. In the general case you probably do though.Innocuous
Also, isn't there a syntax error in doing ... .subscribe(data => this.zone.run() => { // event handler code here }), as I don't understand how it works?Diabetes
You are right, I forgot ( and ) - fixed. Thanks for the hint. If you bootstrap two components from the same AppModule, then you also don't need zone.run(), only if you want to communicate between two applications that are bootstrapped individually. In some cases ChangeDetectorRef.markForCheck(), ChangeDetectorRef.detectChanges(), ApplicationRef.tick(), setTimeout(...), or | async will have a similar effect and invoke change detection. zone.run() is really just the most generic case.Innocuous
This could work but it's not an elegant solution. the right way of doing this is implementing a static method forRoot() and returning a unique instance of your services. Have a look here: https://mcmap.net/q/356446/-what-is-the-best-way-to-share-services-across-modules-in-angular2Ries
@Ries you are right. I didn't undetstand modules properly back then and mixed it up with similar questions about sharing between individual Angular applications which were asked frequently earlier.Innocuous
providedIn: 'platform' not fixed the problem. Could you please have a look at #66138469Heterogony
J
3

The answer that Günter Zöchbauer gave is great, however, one issue you may have is that

window.sharedService

will cause an error in TypeScript. You can get around this by replacing all instances of

window.sharedService

with

(<any>window).sharedService
Joule answered 20/10, 2016 at 16:45 Comment(2)
Thanks @Ryan for the addition. Little busy in other part of the application. will come to that part in few days.Chasechaser
You could also extend the window interface: interface Window { sharedService: any } and avoid multiple repetiton of that change with <any>.Ric
B
2

I attempted to use the ideas in this answer to share data between multiple bootstrapped modules in my angular application. The data I was interested in was coming from an http.get() call. It was an expensive call and I wanted to only make the call once and share its results between my 2 bootstrapped modules.

Initially, I stored the service as a property on my global window object as suggested, but in the end, I decided to use static variables and call publishReplay() on my observable to create a ReplaySubject to replay my emissions to future subscribers. This allowed multiple subscribers to share data and only make the REST call once.

One caveat here... The original question asked how to share one service... This answer does not share one service... Multiple services are created, but the data is shared as the observable is stored in a static variable... That means the observable sticks around as long as your application and because of the call to publishReplay() anyone who subscribes to the observable replays the data previously returned.

Here is my code:

import { Injectable } from '@angular/core';
import { Observable, ReplaySubject } from 'rxjs/Rx';
import { Http } from '@angular/http';

@Injectable()
export class InitService {

    static observable: Observable<number> = null;

    constructor(private http: Http) {}

    getInitData(): Observable<number> {
        if (InitService.observable == null) {
            InitService.observable = this.http.get('api/Init')
                .map(this.extractData)
                .publishReplay()
                .refCount()
                .catch(this.handleError);
        }
        return InitService.observable;
    }


    extractData(res: Response) {
        let body: any = res.json();
        return body || {};
    }

}

Hope this helps.

Bilge answered 11/6, 2017 at 21:0 Comment(0)
G
2

You can't use @Günter Zöchbauer's answer if you are injecting any dependency to the service. My solution to services that need additional dependencies is assign them to the window object in the service constructor.

export class MyService {
  private Cahces = {};
  constructor(protected http: HttpClient, private logger: LogService) {
    if (!(<any>window).myService) {
      (<any>window).myService = this;
    }
    return (<any>window).myService;
  }
}

This will ensure there is only one service no matter how service provided on modules.

Gamb answered 10/7, 2020 at 1:18 Comment(0)
D
1

I think below is a neat and clean solution. I used in many projects.

Create a shared module (AdModule in my case)

/// ad.module.ts

import ...

// create shared service(AdmobService in my case) object, pass constructor params (if you required)
const admobService = new AdmobService(new AdMobFree(), new InAppPurchase()); 

@NgModule({
  providers: [
    ...,
    { provide: AdmobService, useValue: admobService }, //  use shared service here like this
  ]
})
export class AdModule { }

import AdModule in any module where you want to use shared service (AdmobService)

Module1

/// module1.ts
@NgModule({
  imports: [
    ...,
    AdModule, // import shared module
  ],
})
export class Module1 { }

Module 2

/// module2.ts
@NgModule({
  imports: [
    ...,
    AdModule, // import shared module
  ],
})
export class Module2 { }
Dinorahdinosaur answered 24/10, 2019 at 7:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.