I have an abstract base class that used to accept a service via constructor injection:
export abstract class BaseEditEventClass<T> extends BaseFormClass {
constructor(protected _service: BaseEditEventService<T>) {
super();
}
}
In the subclasses, I could provide a more specific service like this:
export class FooBarComponent extends BaseEditEventClass<FooBar> {
constructor(protected override _service: FooBarService) {
super(_service);
}
}
Now that Angular recommends using the inject() function instead of constructor injection, I tried this in the base class:
export abstract class BaseEditEventClass<T> extends BaseFormClass {
protected readonly _service = inject(BaseEditEventService<T>);
}
But then I don’t see how the child class can override that with its own service, e.g. FooBarService.
export class FooBarComponent extends BaseEditEventClass<FooBar> {
// how do I inject FooBarService instead of BaseEditEventService<T> ?
}
What’s the correct way in Angular to let subclasses provide their own injected service when the base class uses inject()?
Should the base class still declare the injection, or should I delegate that responsibility to the subclass?
Is there an idiomatic Angular pattern for this with generics?
Let's try this. The old way is to use a constructor which REQUIRES a parameter. The new way is to use `inject()`. The primary purpose of super() in the subclass constructor is to call the parent class's constructor. If the parent's constructor REQUIRES arguments (like the service in the old way), you MUST provide them via super(argument);
So, since the new base class does not have constructor parameters, the subclass does not need to pass any arguments up.
import { BaseFormClass } from './base-form.class';
import { BaseEditEventService } from './base-edit-event.service';
// base-edit-event.class.ts - assuming the paths
export abstract class BaseEditEventClass<T> extends BaseFormClass {
protected abstract readonly _service: BaseEditEventService<T>;
// there is no explicit constructor, so it has a default, empty one: constructor() {}
}
@Component({
selector: 'app-foo-bar',
templateUrl: './foo-bar.component.html',
providers: [
{ provide: BaseEditEventService, useClass: FooBarService }
]
})
export abstract class BaseEditEventClass<T> extends BaseFormClass {
// no need to inject the service here, the base class handles it.
// the 'providers' array ensures the correct service is injected.
}
@Component({
selector: 'app-foo-bar',
templateUrl: './foo-bar.component.html',
// no providers needed here for this pattern unless you have other specific services you need injected
})
export class FooBarComponent extends BaseEditEventClass<FooBar> {
protected readonly _service = inject(FooBarService);
// no constructor here
// If your subclass has its own constructor logic or needs to inject other dependencies via the constructor (I no longer do this) then you will need a constructor and a call to super();
}
In this case, TypeScript
automatically provides a default constructor which implicitly calls the parent's default (and EMPTY) constructor. You don't need to write anything.