Which @angular/* package(s) are relevant/related to the feature request?
core
Description
Currently providers can be provided only in component or module or root or etc.. but not inside other providers.
- One issue is that one cannot create a provider which depends upon another provider privately.
- Another issue is multiple instances of the same provider which are linked to the provider scope it is provided in.
An example of a case that currently suffer from those problems:
@Injectable()
class CounterService implements OnDestroy {
ngOnDestroy(): void {}
}
const primaryCounterToken = new InjectionToken<CounterService>(
'primaryCounterToken'
);
const primaryCounterProvider: Provider = {
provide: primaryCounterToken,
useClass: CounterService,
};
const secondaryCounterToken = new InjectionToken<CounterService>(
'secondaryCounterToken'
);
const secondaryCounterProvider: Provider = {
provide: secondaryCounterToken,
useClass: CounterService,
};
@Injectable()
class TwoCountersService {
constructor(
@Inject(primaryCounterToken) primary: CounterService,
@Inject(secondaryCounterToken) secondary: CounterService
) {}
}
const twoCountersProviders: Provider[] = [
primaryCounterProvider,
secondaryCounterProvider,
TwoCountersService,
];
In this case CounterService has some logic but in TwoCountersService two instances of it are required. The solution here is using InjectionToken to create two instances and inject them. The second issue mentioned above is yet to be seen in this example, but the first issue is.
Lets assume I want to use this TwoCountersService in a component - I need to provide all three tokens in the component providers - hence the twoCountersProviders array. Ideally I would like to just specify TwoCountersService.
Now lets assume I want two instances of TwoCountersService just like I wanted with CounterService. At this point I am stuck due to the second issue - as I actually need 4 instances of CounterService which is not possible this way.
The only solution is using a factory provider:
const counterFactoryToken = new InjectionToken<CounterService>(
'counterFactoryToken'
);
const counterFactoryProvider: Provider = {
provide: secondaryCounterToken,
useFactory: () => () => new CounterService(),
};
@Injectable()
class TwoCountersService {
private primary = this.counterFactory();
private secondary = this.counterFactory();
constructor(
@Inject(counterFactoryToken) private counterFactory: () => CounterService
) {}
}
// Two instances:
const firstTwoCountersToken = new InjectionToken<TwoCountersService>(
'firstTwoCountersToken'
);
const firstTwoCountersProvider: Provider = {
provide: firstTwoCountersToken,
useClass: TwoCountersService,
};
const secondTwoCountersToken = new InjectionToken<TwoCountersService>(
'secondTwoCountersToken'
);
const secondTwoCountersProvider: Provider = {
provide: secondTwoCountersToken,
useClass: TwoCountersService,
};
Now i can safely inject firstTwoCountersToken and secondTwoCountersToken and have two instances and get 4 new instances of CounterService.
The first issue is even more apparent here - I need to provide now: counterFactoryProvider, firstTwoCountersProvider and secondTwoCountersProvider and if there was a service injecting both - that service too.
The second issue is solved here.
But - I lose lifecycle methods (ngOnDestroy) this way which isn't what I wanted.
So instead of factory provider I need a service and manually handle lifecycle... something like:
@Injectable()
export class CounterServiceFactory {
private registry: CounterService[] = [];
getNew() {
const srv = new CounterService();
this.registry.push(srv);
return srv;
}
ngOnDestroy(): void {
for (const srv of this.registry) {
srv.ngOnDestroy();
}
this.registry = [];
}
}
And I got it working with A LOT of code and roundabout issues to solve. It still requires annoyingly providing providers carefully and a factory service for every service.
Proposed solution
If it was possible to provide providers inside @Injectable - it could be solved nicely:
@Injectable()
class CounterService implements OnDestroy {
ngOnDestroy(): void {}
}
const primaryCounterToken = new InjectionToken<CounterService>(
'primaryCounterToken'
);
const primaryCounterProvider: Provider = {
provide: primaryCounterToken,
useClass: CounterService,
};
const secondaryCounterToken = new InjectionToken<CounterService>(
'secondaryCounterToken'
);
const secondaryCounterProvider: Provider = {
provide: secondaryCounterToken,
useClass: CounterService,
};
@Injectable({
providers: [primaryCounterProvider, secondaryCounterProvider]
})
class TwoCountersService {
constructor(
@Inject(primaryCounterToken) primary: CounterService,
@Inject(secondaryCounterToken) secondary: CounterService
) {}
}
const firstTwoCountersToken = new InjectionToken<TwoCountersService>(
'firstTwoCountersToken'
);
const firstTwoCountersProvider: Provider = {
provide: firstTwoCountersToken,
useClass: TwoCountersService,
};
const secondTwoCountersToken = new InjectionToken<TwoCountersService>(
'secondTwoCountersToken'
);
const secondTwoCountersProvider: Provider = {
provide: secondTwoCountersToken,
useClass: TwoCountersService,
};
Now if I need firstTwoCountersProvider and secondTwoCountersProvider - that's only what I provide and inject and all the different instances are handled automatically as providers provided in @Injectable are sharing the providing scope.
Even better if there was a service injecting both - I would just provide that service ONLY and inject it.
Alternatives considered
The long and complicated solution from above.
Which @angular/* package(s) are relevant/related to the feature request?
core
Description
Currently providers can be provided only in component or module or root or etc.. but not inside other providers.
An example of a case that currently suffer from those problems:
In this case
CounterServicehas some logic but inTwoCountersServicetwo instances of it are required. The solution here is usingInjectionTokento create two instances and inject them. The second issue mentioned above is yet to be seen in this example, but the first issue is.Lets assume I want to use this
TwoCountersServicein a component - I need to provide all three tokens in the component providers - hence thetwoCountersProvidersarray. Ideally I would like to just specifyTwoCountersService.Now lets assume I want two instances of
TwoCountersServicejust like I wanted withCounterService. At this point I am stuck due to the second issue - as I actually need 4 instances ofCounterServicewhich is not possible this way.The only solution is using a factory provider:
Now i can safely inject
firstTwoCountersTokenandsecondTwoCountersTokenand have two instances and get 4 new instances ofCounterService.The first issue is even more apparent here - I need to provide now:
counterFactoryProvider,firstTwoCountersProviderandsecondTwoCountersProviderand if there was a service injecting both - that service too.The second issue is solved here.
But - I lose lifecycle methods (
ngOnDestroy) this way which isn't what I wanted.So instead of factory provider I need a service and manually handle lifecycle... something like:
And I got it working with A LOT of code and roundabout issues to solve. It still requires annoyingly providing providers carefully and a factory service for every service.
Proposed solution
If it was possible to provide providers inside @Injectable - it could be solved nicely:
Now if I need
firstTwoCountersProviderandsecondTwoCountersProvider- that's only what I provide and inject and all the different instances are handled automatically as providers provided in@Injectableare sharing the providing scope.Even better if there was a service injecting both - I would just provide that service ONLY and inject it.
Alternatives considered
The long and complicated solution from above.