28

I have abstract class(without constructor) and I want to inject another class into it.

Abstract class:

import { ErrorHandler } from '../../shared/services/errorHandler.service';
import { Inject } from '@angular/core';


export abstract class BaseApiComponent<T> {
    @Inject(ErrorHandler) errorHandler: ErrorHandler;

    this.errorHandler.test();
}

Injected class:

import { Injectable } from '@angular/core';

@Injectable()
export class ErrorHandler  {
  constructor() { }


  public test() {
    console.log('Test');
  }

}

And I have next error

ORIGINAL EXCEPTION: TypeError: Cannot read property 'test' of undefined

How i can fix this?

4 Answers 4

31

As of Angular 2 RC5 the DI becomes simpler. You don't need to decorate the stuff with @Injectable(). Instead, you just declare it for DI in one place - NgModule.

export class ErrorHandler {
    test() {
        console.log('ErrorHandler.test()');
    }
}

export abstract class BaseApiComponent<T> {
    // use protected property parameter in abstract class
    // for accessibility from descendants.
    constructor(protected errorHandler: ErrorHandler) {}

    someMethod() {
        this.errorHandler.test();
    }
}

export class ApiComponentImpl<T> extends BaseApiComponent<T> {
    // use @Inject decorator
    constructor(@Inject(ErrorHandler) errorHandler: ErrorHandler) {
        super(errorHandler);
    }
}

app.module.ts:

// class declarations
@NgModule({
    providers: [
        ErrorHandler,
        ApiComponentImpl
    ]
})
export class AppModule{
}

// bootstrap the app
platformBrowserDynamic().bootstrapModule(AppModule);

You can employ OpaqueToken to improve modularity and remove type dependency:

export const errorHandlerToken = new OpaqueToken('ErrorHandler');

in the module:

    providers: [
        // using OpaqueTokens you can change the provided value
        // to anything without changing consumer code 
        {provide: errorHandlerToken, useClass: ErrorHandler},

constructor:

    constructor(@Inject(errorHandlerToken) errorHandler: ErrorHandler) {
Sign up to request clarification or add additional context in comments.

Comments

17

Angular DI only supports constructor injection, which means you need a constructor.

You also can't inject into an abstract class directly because an abstract class is not supposed to be instantiatable.

Therefore it has to be like:

export abstract class BaseApiComponent<T> {
    constructor(errorHandler: ErrorHandler) {}

    someMethod() {
     this.errorHandler.test();
    }
}

export class ApiComponentImpl<T> {
    constructor(errorHandler: ErrorHandler) {
      super(errorHandler);
    }

}

3 Comments

I don't see why. I don't know TS too well but usually abstract classes can have concrete implementations (properties, methods) and they need to be initialized.
It make sense. But just tried to inject an abstract service class because I wanted to show my colleague that you can't do that. The is class decorated with @injecatble and provided in root and it seems like it works. Do you know why? stackblitz.com/edit/angular-ivy-nbpvtg?file=src/app/…
Interesting catch. Seems Angular doesn't generate new Lala(...) code but creates the instance by some other way there the compiler can't catch that an abstract class is instantiated.
2

I'm not an angular developer, but looking at the examples the @Inject decorator is always used as a parameter decorator and not as a property decorator.

As the two decorator types are different, that might cause the problem but I'm not sure.
Try:

export abstract class BaseApiComponent<T> {
    private errorHandler: ErrorHandler;

    protected constructor(@Inject(ErrorHandler) handler) {
        this.errorHandler = handler;
    }

    public error() {
        this.errorHandler.test();
    }
}

Also I'm not sure when you're actually using this.errorHandler.test(); as it can't just sit there in the class, I moved it into the error method.


edit

Right. You'll need to inject in the extending class and then pass the instance to the parent:

export abstract class BaseApiComponent<T> {
    protected errorHandler: ErrorHandler;

    protected constructor(handler: ErrorHandler) {
        this.errorHandler = handler;
    }
}

export class Restaurants extends BaseApiComponent<any> {
    constructor(@Inject(ErrorHandler) handler) {
        super(handler);
    }
}

2 Comments

I am inheriting from BaseApiComponent export class Restaurants extends BaseApiComponent<any> { constructor(private apiService: ApiService) { super(); } And i have error with this constructor : "Supplied parameters do not match any signature of call target" Maybe I can inject it without constructor?
Oh. Now I see what you mean. Try my revised answer
1

You can declare service you need as private or protected member of target abstract class and then use inject function in its constructor:

import { inject } from '@angular/core';

export abstract class BaseApiComponent<T> {
    
    protected errorHandler: ErrorHandler;

    constructor() {
        this.errorHandler = inject(ErrorHandler);
    }

    testService() {
        this.errorHandler.test();
    }
}

And you can use it in the implementations of this abstract class:

@Component({...})
export class MyComponentImplementation extends BaseApiComponent<T> {
    override testService() {
        super.testService();
        this.errorHandler.test();
    }
}

This solution is an approach to inject dependencies to abstract classes without touching their implementations.

1 Comment

Note, you can just do protected errorHandler: ErrorHandler = inject(ErrorHandler) - that compiles to the syntax you have but it looks cleaner.

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.