1

I have a relatively simple setup with three classes. I am using inversify for dependency injection. But when injecting the class MessageBroker into the derived class Repository the MessageBroker is undefined:

import 'reflect-metadata';
import { injectable, inject, Container, unmanaged } from 'inversify';

const container = new Container();

const registerProviders = (...providers: any[]) =>
  providers.forEach(provider => container.bind(provider.name).to(provider));

const getProvider = (provider): any => container.get(provider.name);

@injectable()
export class MessageBroker {
  start = () => console.log('init message broker');
}

@injectable()
export abstract class Repository {
  @inject(MessageBroker.name) private mb: MessageBroker;

  constructor(@unmanaged() protected readonly user: any) {}

  // this.mb is undefined
  initialize = () => this.mb.start();
}

@injectable()
export class UserRepository extends Repository {
  constructor() {
    super({ user: 'some object' });
    this.initialize();
  }
}

registerProviders(UserRepository, Repository, MessageBroker);

const repo: UserRepository = getProvider(UserRepository);

You can try it yourself. I've created a small GitHub repository: https://github.com/flolude/stackoverflow-inversify-injected-service-undefined

When running the script, I get this error:

/project/index.ts:22
  initialize = () => this.mb.start();
                             ^
TypeError: Cannot read property 'start' of undefined
    at UserRepository.Repository.initialize (/project/index.ts:22:30)
    at new UserRepository (/project/index.ts:29:10)
    at _createInstance (/project/node_modules/inversify/lib/resolution/instantiation.js:21:12)
    at Object.resolveInstance (/project/node_modules/inversify/lib/resolution/instantiation.js:41:18)
    at /project/node_modules/inversify/lib/resolution/resolver.js:72:42
    at Object.resolve (/project/node_modules/inversify/lib/resolution/resolver.js:96:12)
    at /project/node_modules/inversify/lib/container/container.js:319:37
    at Container._get (/project/node_modules/inversify/lib/container/container.js:310:44)
    at Container.get (/project/node_modules/inversify/lib/container/container.js:230:21)
    at getProvider (/project/index.ts:9:50)

P.S. I get pretty much the same error when compiling the code to Javascript

Florian Ludewig
  • 4,338
  • 11
  • 71
  • 137

1 Answers1

1

Your MessageBroker has only been set in memory but has never been instantiated, which is how it is getting the undefined error. In your constructor you will need to set

this.mb = new MessageBroker();

Another way you can do this without the above line is to add a empty parameter signatured constructor into the MessageBroker class.

Jake
  • 11
  • 1
  • 2
    Doesn't it defeat the whole purpose of dependency injection? Would be cool if you'd show an example for your second method :) – Florian Ludewig Sep 19 '19 at 19:22
  • DI still requires something that exists. `export class MessageBroker { constructor() {} start = () => console.log('init message broker'); }` – Jake Sep 19 '19 at 19:31
  • Another example, this is an Angular 4 Example, but still uses Typescript nonetheless: `@Injectable() export class ReferenceNumberTableService { constructor(private globals: GlobalService, private http: Http) { } //These GlobalService is also an Injectable Service. and Http is an @angular/core import. //Other methods here. } ` Used here in the component file: `constructor(readonly referenceNumberTableService: ReferenceNumberTableService) { } ` – Jake Sep 19 '19 at 19:43
  • The empty constructor doesn't work. Calling `this.mb = new MessageBroker();` does work. But I don't get why I need to create a new instance. I usually don't have to do this with DI – Florian Ludewig Sep 20 '19 at 05:02
  • 1
    `@inject(MessageBroker.name) private mb: MessageBroker;` isn't actually inject it the way you need it to. If you wanted to inject it without a new instance of Message Broker you would have to put the MessageBroker in your Repository constructor by adding: private mb: MessageBroker and deleting the above mentioned line. You can see how I did that in my example where ReferenceTableService is an Injectable Service and I stuck it in the component's constructor. That would also work. Without you doing that you need to make a new instance. The constructor setup would create a new one for you. – Jake Sep 20 '19 at 13:30
  • Sorry I been responding to this at work so my brain has been focusing on that. – Jake Sep 20 '19 at 13:31
  • 1
    Ok that makes sense. But then I get an error telling me that I need to provide an argument in the `super()` of the `UserRepository`... So I guess I have to inject the `MessageBroker` into the `UserRepository` and pass it down through the `super()` ? – Florian Ludewig Sep 20 '19 at 14:55