4

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?

2 Answers 2

4

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.

Sign up to request clarification or add additional context in comments.

3 Comments

If I understand correctly, if I REQUIRES the service to exists, there is no way to override his value without the super(argument) ?
Yes, that's exactly right. For the classic constructor injection pattern, super(argument) is the only way to pass the required dependency up to the parent. The new way with inject() and providers decouples this nicely. I ONLY use constructor for signals. However, if you do use constructor() the VERY first call inside it MUST be super();
Ok thanks, So I cannot do what I'd like and will have to stay with the super I guess... :/ Thanks for the explanation though
0

Just use override the child reference will override the base class instance and it will work fine.

export interface FooBar {

}
export class BaseFormClass {

}
export class BaseEditEventService<T> {
  
}
export class FooBarService<T> extends BaseEditEventService<T> {

}
export class test {
  
}
export abstract class BaseEditEventClass<T> extends BaseFormClass {
  protected readonly _service: BaseEditEventService<T> = inject(BaseEditEventService<T>);
}

export class FooBarComponent extends BaseEditEventClass<FooBar> {
  // how do I inject FooBarService instead of BaseEditEventService<T> ?
  protected override readonly _service = inject(FooBarService);
}

12 Comments

I've tried that one, but it does not work as the services is trying to instantiate BaseEditEventService first, then FooBarService :/
@RaphaëlBalet you mean FooBarServicedoes not inherit BaseEditEventService reference stackblitzif inheritance is present, then base class always initializes first
I mean when calling FooBarComponent, BaseEditEventService need to be provided. If I do not provide it, then it won't work. But I'd like to provide only the FooBarService as I won't use the other one. Hope it makes sense '^^
@RaphaëlBalet What do you mean by "need to be provided" can you please modify the stackblitz with comments on what you need and share back
I'm really sorry, but this does not seems to be true, here's a stablitz reproduction of your code. You'll see in the console that BaseService need to be provided, even if I do not use it. Please, let me know how you're handling what you're saying in my stackblitz, maybe I misunderstood something. For me, it seems like you're providing your services in the root, therefore they are instantiated by default and you are "blind" to the error.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.