I notice that my project has a lot of resolver services that despite from working with different entities and repositories host the same code. So I took the challenge to reduce them to a single resolver service using generics:
@Injectable()
export class DetailResolver<T, R extends Repository<T>> implements Resolve<T> {
constructor(private repositoryService: R, private router: Router) {}
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<T> {
// use repositoryService to resolve this route
}
}
The entity type T and the repository type R can be specified. However I have trouble using this service:
const appRoutes: Routes = [
// other routes
{
path: ':id',
component: MessageEditComponent,
resolve: {
message: DetailResolver<Message, MessageService>
}
}
];
@NgModule({
imports: [
RouterModule.forChild(appRoutes)
],
exports: [
RouterModule
],
providers: [
DetailResolver<Message, MessageService>
]
})
As soon as I specify the generic type in DetailResolver<Message, MessageService>
the compiler wants me to create an instance:
Value of type typeof DetailResolver is not callable. Did you mean to include new?
I am not really familiar with angular4 DI internals. Can anybody describe whats wrong, is there any solution to this problem? I am using angular 4.3.
When you register a provider with an injector, the DI system uses that token—or what could be otherwise seen as a key—to maintain a token-provider map that is referenced every time that a dependency is asked for.
Tokens are generally objects that are unique and symbolic, so the concept of generics directly conflicts in this area precisely because the token is going to function as a key. Generics are a design-time artifact, which is another way of saying that they will disappear from the generated JavaScript and therefore won't leave their type information for Angular to find at runtime. TypeScript interfaces are also not valid tokens for the same reason.
Abstract classes
My first option would be to use abstract
classes. Abstract classes solve two problems here:
1) While they cannot be instantiated directly, unlike an interface they can contain implementation details and therefore can be compiled into Javascript.
2) You obtain a DI token via an architecture based on extending, which might work very well for your architecture.
In your case you could do something like the following:
@Injectable()
export abstract class DetailResolver<T, R extends Repository<T>> implements Resolve<T> {...}
@Injectable()
export class MessageResolver extends DetailResolver<Message, MessageService> {...}
And then in the NgModule
you would provide it in the following manner:
providers: [{ provide: DetailResolver, useClass: MessageResolver }]
InjectionToken
Another option is the use of InjectionToken
(known as OpaqueToken
s prior to Angular 4.0). InjectionToken
s are objects that are exclusively used as DI tokens; however unlike they're predecessor, OpaqueToken
, they require to be typed as to what is the type of value they will inject.
const MESSAGE_RESOLVER = new InjectionToken<DetailResolver<Message, MessageService>>('MESSAGE_RESOLVER');
You'll notice that you can provide the type as you're creating the InjectionToken
instance to have the compiler support you along the way. That is enforced when you add the provider in your NgModule
.
providers: [{ provide: MESSAGE_RESOLVER, useClass: MessageResolver }]
It's worth noting that useClass
is not technically required there; any of the other alternatives such as useValue
or useFactory
are valid here when providing the corresponding.
Hope this is helpful and provides some clarification on the matter!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With