Solvedangular Sharing a provider instance between two components in different subtrees
✔️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
andWishList
are both used in the parentWishListManager
component. - He removes
WishStore
dependency fromWishList
andWishDetail
. - He adds
WishStore
dependency to theWishListManager
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
'
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 aData
service because they declareData
in their providers but theApp
using theItem
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
androute2
who are lazy loaded.route1
androute2
use aData
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 asData
will be loaded even if we never visitroute1
androute2
.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:
or
We can then imagine the following usage:
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.