Solvedangular Sharing a provider instance between two components in different subtrees

Hi !

I'm thinking about this issue.

Suppose you are using a component or different components from a cool library you found on NPM. These components might need to share a singleton of a service and you don't want all the apps or components using this library to declare the providers related to these components.

We need a way to allow different components at different places to share the same singleton.

Here is a plunker with two Item components using two different instances of a Data service because they declare Data in their providers but the App using the Item components is not aware of that service.

https://plnkr.co/edit/OuopKs?p=preview

In order to share the same singleton, I came up with the following design pattern.

https://plnkr.co/edit/uNrW6q?p=preview

It works and it is possible to customize the DataFactory in order to get the singletons by id or something.

Why do we really need this?

Imagine we have two routes route1 and route2 who are lazy loaded. route1 and route2 use a Data service and need to share the same instance.
We can put Data in the app's providers but then we loose the benefits of lazy loading as Data will be loaded even if we never visit route1 and route2.

The idea is to declare dependencies as late a possible and still keep the same singleton.

It would be really nice to have something generic like:

@Component {
}
class ItemComponent {

    constructor(@InjectSingleton(Data) data: Data) {
    }

}

or

@Component {
}
class ItemComponent {

    @Input dataId: string;

    constructor(private dataFactory: Factory<Data>) {}

    ngOnInit() {

        /* Get a global singleton. */
        dataFactory.getDefault();

        /* Get a singleton by id. */
        dataFactory.get(this.dataId);
    }
}

We can then imagine the following usage:

<div *ngFor="let item of itemList">
    <wt-item [dataId]="item.groupId()"></wt-item>
</div>

Items from the same group will share the same service.

Well this is an advanced use case but just having a global app singleton would be a nice first step.

Thank you in advance for your feedback.

19 Answers

✔️Accepted Answer

@escardin ???
I think it's exactly the opposit. I'm trying to make the dependencies more explicit.

Suppose you have a Wishlist app which is using two different components WishList and WishDetail.
Both need the singleton of the WishStore service.

Imagine this real life scenario:

Day 1:
I write the WishList component and WishStore service and I of course I naively add WishStore in WishList providers.

Day 2:
Another developer writes the WishDetail component and adds WishStore service in WishDetail providers...
Bouh... components work but the app doesn't as the two components are not sharing the same instance of WishStore.
To fix this, here's what he does:

  • He discovers that WishDetail and WishList are both used in the parent WishListManager component.
  • He removes WishStore dependency from WishList and WishDetail.
  • He adds WishStore dependency to the WishListManager component.

It works!

Day 3:
We write the Admin component which needs WishList component.
A naive developer would add the WishStore to the Admin component's dependencies but then we'll have another instance of WishStore for the Admin component and we don't want that.
A smarter developer would add the WishStore to the App dependencies...

So, there are two solutions for every shareable service we use.
1 - We add it as an app dependency which is ugly as we'll end up with all services loaded by the app without knowing which components are using them... and maybe they are not used anymore.
2 - We add them to the highest common parent component but this can change regularly so we'll be refactoring all the time.

What I want is to declare WishStore as a direct dependency of WishDetail and WishList but still share the same instance.

Here are the advantages of this approach:

1 - Dependencies are explicit, I can see that WishStore is a dependency of WishDetail and WishStore.
2 - Nobody can break my app by shadowing my App's WishStore dependency.
3 - The day, developer 1 removes WishStore from WishDetail and developer 2 removes WishStore from WishList, WishStore won't be loaded anymore.
4 - If WishDetail and WishList are loaded lazily, WishStore won't be loaded until WishDetail or WishList are loaded.

Otherwise, we'll end up with same issues as AngularJS... In one year, all our apps will have hundreds of dependencies on the root component or bootstrap and we'll load tons of useless code if don't cleanup manually.

Meanwhile, the design pattern I'm encouraging people to put the list of providers in static property of each component and it would look like this:

@Component({
  providers: [WishListManager.PROVIDERS, Admin.PROVIDERS]
})
class AppComponent {}

class WishListManager {
  static PROVIDERS = [WishList.PROVIDERS, WishDetail.PROVIDERS]
}

class Admin {
  static PROVIDERS = [WishList.PROVIDERS, WishDetail.PROVIDERS]
}

class WishList {
  static PROVIDERS = [WishStore]
}

class WishDetail {
  static PROVIDERS = [WishStore]
}

Problem with this solution:
Suppose that Admin component is a lazy loaded route. By Importing Admin because I need Admin.PROVIDERS, I will lose the benefits of lazy loading.

This is really a common issue.
Have a look at @vsavkin's router@3.0.0 plunker: http://plnkr.co/edit/ER0tf8fpGHZiuVWB7Q07?p=preview

AppComponent has HeroService in it's dependencies while HeroService is a private matter of HeroListComponent and HeroDetailComponent. So even if these components are loaded lazily, HeroService will be loaded no matter what.
Plus, if we decide to remove these components, nobody will (and has) to think about removing HeroService from AppComponen.

Now look at AppComponent again, where is CrisisService dependency declared? You'll have to browse the component tree logic to find out it's declared as a dependency of CrisisCenter and what if you want to reuse CrisisDetail in another component... well you'll have to refactor and move CrisisService to AppComponent.

Can anyone validate this issue or tell us how one can handle this in a big app with hundreds of services without loosing benefits of lazy loading.

I feel so alone with this issue :)

Other Answers:

@yjaaidi No i wrote internal project, so you can import NgModule with needed providers into tree where you need single instance (i my project it global so i used OwnNgModule.forRoot() only for root NgModule) but for others i simply import simple OwnNgModule. So if in some tree component you wish to create own provider you import OwnNgModule.forRoot() or something else for override it.
But yes it propagate to tree.
Example of module

@NgModule({
             .... without providers or providers without singleton
          })
export class OwnNgModule {
    static forRoot(): ModuleWithProviders {
        return {
            ngModule:  OwnNgModule ,
            providers: [......providers with needed singleton.]
        };
    }

    static forChild(): ModuleWithProviders {
        return {
            ngModule:  OwnNgModule 
        };
    }
}

for root NGModule use imports:[OwnNgModule.forRoot()]
for child modules you can use simple: imports:[OwnNgModule]

I'm not talking about a @global decorator.

Just one that prevents shadowing @NoOverride that triggers an explicit error:
'UserStore is flagged NoOverride and it has been loaded by AppComponent and it's child UserDetailComponent, remove it from UserDetailComponent's providers to use the instance from AppComponent'

Related Issues:

608
angular Angular5.x lazyLoad problem, undefined is not a function
For others that find this issue via Google as i did: I had the same problem when trying to lazy load...
348
angular Cyclic dependency error with HttpInterceptor
I resolved simply not setting authService in constructor but getting in the intercept function. ...
277
angular Uncaught Error: Can't resolve all parameters for ...
You are missing an @Injectable() annotation on your ApiService Support requests like these should li...
266
angular Force reload/refresh current route with RouteReuseStrategy
Hi If you really need to trick the Router into reloading the component on each routerLink click ...
260
angular Misleading error message "Cannot find a differ supporting object '[object Object]'"
I just ran into the same issue I'm not sure if the recommended solution will work for my case ...
224
angular update 2 to 4 has problem [ts] Property 'map' does not exist on type 'Observable<Response>'.
I met the same problem with the angular cli 6.0.0 and rxjs 6.1.0 And I solved the problem by replaci...
170
angular Angular2 AoT Compiler Errors
pls try /cc @chuckjaz When I try to compile my project with ngc it throws the below error: Error: Er...
152
angular HttpClient.delete() cannot handle a body in its request
It would be great to have body param in .delete() We just migrated our project to HttpClient and for...
140
angular Http - Observable completed function not firing
Third callback haven't been called when error occures ES6 promises hasn't method finally only then a...
133
angular Using multiple components in different modules causing "Type X is part of the declarations of 2 modules" error
as @brandonroberts saids create a shared module like this: then use the SharedModule like this.. ...
112
angular Unsupported platform for fsevents@1.0.14: wanted {"os":"darwin","arch":"any"} (current: {"os":"win32","arch":"x64"})
@DzmitryShylovich did you edit package.json only? if npm-shrinkwrap.json is still there please remov...
98
angular Angular v5 ngc compiler: Error encountered in metadata generated for exported symbol 'Subscription'
Got this problem I'm submitting a.. Current behavior Building an Angulary library using 5.0.0-beta.4...
98
angular 404 on route refresh in angular 4
Got it working Just adding .htaccess in root I'm submitting a.. Current behavior I created a new com...
97
angular routerLinkActive not updating when routerLink changed
I have a hack that seems to work After looking at the source code it looks like this.update() is als...
97
angular No provider for HttpClient!
If you are using angular v5 version import HttpClientModule in your app.module.ts after HttpModule T...
81
angular ɵDomAnimationEngine and ɵNoopAnimationEngine module missing in animations 4.2.1
@dubedoy I installed @angular/animations@4.1.3 and it worked again. I'm submitting a .. ...
80
angular Error: Runtime compiler is not loaded in angular6 --prod mode
Do not and i repeat do not import your feature modules in app module and also when addressing featur...
79
angular appending header in HttpHeaders from '@angular/common/http' doesn't work
@trotyl I didn't understand your comment I'm submitting a.. ...
77
angular Can't bind to 'formGroup' since it isn't a known property of 'form'
did you import ReactiveFormsModule? I'm submitting a .. ...
73
angular AOT Compiler requires public properties, while non-AOT allows private properties
@aluanhaddad you have a big misunderstanding in here There is no subset of Typescript in here No one...
73
angular [Bug] angular/elements: Failed to construct 'HTMLElement': Please use the 'new' operator
Hi I have solved this issue by changing the target:es5 in the tsconfig.json to target:es2015 these i...
70
angular Cannot run angular 2+ from file:/// - looks like 'base href="/"' is the issue
Thanks @Markovy @audrummer15 I got it working completely in a fairly complex angular 2 app with mult...
69
angular HttpClient fails to parse an empty 200 response in IE11
For my error I was able to fix the problem by setting the responseType: to 'text' in the options ...
66
angular Function calls are not supported in decorators when fullTemplateTypeCheck is not specified and @dynamic has no effect
Regarding Ward's repro: @wardbell The build will succeed / fail depending on the combination of angu...
62
angular error TS2451: Cannot redeclare block-scoped variable 'ngDevMode'
had to add this line in the main tsconfig I'm submitting a.. ...
62
angular Problem with ngFor
Wouldn't [(ngModel)]=testItems[i] do the trick? I think that the error is saying that you can assign...
61
angular Issue with importing Observable from rxjs/Rx (import-blacklisted)
You shouldn't import from 'rxjs' or 'rxjs/Rx' since either import will import the whole of rxjs whic...
53
angular [RC5]: Minified bundle breaks
@robertoforlani Hopefully someone will have time to write a comprehensive explanation soon In the me...
53
angular router-outlet is appending rather than replacing when using BrowserAnimationsModule
Trying this solved the problem for me: this.zone.run(() => { this.router.navigate(['/main']); }); Re...
52
angular Lazy loaded module in named outlet throws error
We have this Where proxy route component is simply [x] bug report [ ] feature request [ ] support re...
52
angular IVY Error NG6002: Appears in the NgModule.imports of AppModule, but could not be resolved to an NgModule class
Not sure this will provide anyone relief or assist with figuring out what the root cause is but clea...
51
angular How to run angular 2 application on apache hosting server
Sorry to rock the boat I hope this doesn't attract more questions I'm only going to comment once :) ...
51
angular Support adding rel=canonical link tags using an included service
Eventually there will be some DocumentService part of Core that will handle both Meta/Link elements ...
50
angular Concept of Angular (ngZone + ChangeDetection) better than concept React, Vue (Virtual DOM)?
Concept of Angular (ngZone + ChangeDetection) better than concept React Vue (Virtual DOM)? If you ca...
50
angular I'd like to be able to use ngModel without specifying a name
Thank you all for the great feedback - very helpful! Here's how we are thinking about it: In the cas...
49
angular Model values not trimming automatically in angular 2
@laskoviymishka White space it already something If you are a programmer and think globally - yes ...
46
angular HttpClient - HttpErrorResponse not json but blob
I created this interceptor as a temporary solution until this one is fixed: I'm submitting a.. ...
45
angular Router's ActivatedRoute data returns empty {} if module is lazy
data is available only with this hell-like construction: And this is if you have children: ...
44
angular Misleading errormessage when using HostBinding with @animation trigger but no defined animations
The error message is not fine The error message says you're importing BrowserAnimationsModule incorr...
42
angular Memory leak when FormControlName created/destroyed few times
This issue has been around for nearly 3 years now (I usually don't like to start a message this way ...
41
angular Remove System.import() usage in favor of import()
I use a parser rule in my webpack configuration to disable the warning: https://webpack.js.org/confi...
41
angular Async event subscriber not updating UI after async call
Hi! The issue is that the async call result is outside ngZone thus not triggering the UI update You ...
40
angular Using router.navigate to navigate to another component does not invoke the onInit method
I have the same issue Angular is running in a Cordova app for iOS I tried the router-version 4.1.3 (...
37
angular Router: AoT compilation fails when using a function with loadChildren
Calling functions or calling new is not supported in metadata when using AoT This includes things li...
35
angular Provide a mock service using TestBed
I was having this issue as well however I noticed that my @component metadata still had the provider...
35
angular Angular2 download excel file from Web API, file is corrupt
@healkar01 I had the same issue and I resolved using native angular2 http request in this way: Backe...
35
angular 4.0.0-rc.6 [platform-server] - Cannot find module '@angular/animations/browser'. & other errors
(Just incase others find it) Make sure @angular/animations is installed as a dependency and the erro...
35
angular HttpClient mapping to typescript types not working
I agree with all the previous comments I find the syntax misleading widget.service.zip widget.servic...