Reputation: 2299
Been trying to implement a genetic resolver in my app for a while now. My first implementation based on my intuition gave me the exact result as this SO question: How to provide a service that uses generics in Angular4?, which of-course also gave me the exact error.
I tried solving it using the excepted answer (using InjectionToken
) but could not get it to work, perhaps I'm not proficient enough in using InjectionToken
, my current code:
GenericResolverService
`export interface GetItem<T> {
getItem(id: number): Observable<T>;
}
@Injectable()
export class GenericResolverService<T, U extends GetItem<T>>
implements Resolve<T> {
constructor(private dataService: U,
private router: Router) {
}
resolve(route: ActivatedRouteSnapshot,
stata: RouterStateSnapshot):
Observable<T> | Observable<never> {
const itemId = +route.paramMap.get('id');
return this.dataService.getItem(itemId).pipe(
take(1),
mergeMap(item => {
if (item) {
return of(item);
} else {
this.router.navigate(['/error/not-found']);
return EMPTY;
}
})
);
}
}`
Router module
const ITEM_RESOLVER =
new InjectionToken<GenericResolverService<ItemMasterData, ItemMasterDataService>>
('itemResolver');
const routes: Routes = [
{
path: ':id', component: ItemMasterDataEditComponent,
resolve: {
item: ITEM_RESOLVER
}
}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
providers: [{ provide: ITEM_RESOLVER, useClass: GenericResolverService}]
})
export class ItemMasterDataRoutingModule { }
ItemMasterDataService
@Injectable()
export class ItemMasterDataService implements GetItem<ItemMasterData> {
public getItem(id: number): Observable<ItemMasterData> {
...
}
The current code throws error:
Error: Can't resolve all parameters for GenericResolverService: (?, [object Object]).
What am I doing wrong?
Upvotes: 1
Views: 446
Reputation: 214017
Angular requires that DI token is available at runtime but you use generic type which will disappear from the generated JavaScript.
If you read attentively the answer to the question you linked the you can notice that the base generic class wasn't provided at all but rather derived class:
providers: [{ provide: MESSAGE_RESOLVER, useClass: MessageResolver }]
^^^^^^^^^^^^^^^^^
which contains specific token in its constructor:
export abstract class DetailResolver<T, R extends Repository<T>> implements Resolve<T> {
constructor(protected repositoryService: R, protected router: Router) {}
}
@Injectable()
export class MessageResolver extends DetailResolver<Message, MessageService> {
constructor(repositoryService: MessageService, router: Router) {
^^^^^^^^^^^^^^
available at runtim
super(repositoryService, router);
}
}
In your example you're still trying to feed class with generic constructor parameters but Angular doesn't know which class to instantiate.
So you can either create derived class as in the example above or leverage alternative recipe for DI(useFactory):
{
provide: ITEM_RESOLVER,
useFactory: (service, router) => new GenericResolverService(service, router),
deps: [ItemMasterDataService, Router]
}
Also, you can use your generic class as a token:
const routes: Routes = [
{
path: ':id', component: ItemMasterDataEditComponent,
resolve: {
item: GenericResolverService
}
}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
providers: [
ItemMasterDataService,
{
provide: GenericResolverService,
useFactory: (service, router) => new GenericResolverService(service, router),
deps: [ItemMasterDataService, Router]
}
]
})
export class ItemMasterDataRoutingModule { }
One more option is to use real token for your dataService
constructor(protected dataService: SomeBaseDateServiceClass) {}
and provide different implementation on different levels of your application:
{ provide: SomeBaseDateServiceClass, useClass: ItemMasterDataService }
Upvotes: 2