Search code examples
node.jstypescriptinversion-of-controlinversifyjstypeorm

How to inject an asynchronous dependency in inversify?


I have TypeScript application and I'm using Inversify for IoC.

I have a connection class:

'use strict';
import { injectable } from 'inversify';
import { createConnection, Connection } from "typeorm";
import { Photo, PhotoMetadata, Author, Album } from '../index';

@injectable()
class DBConnectionManager {

    public createPGConnection(): Promise<Connection> {
        return createConnection({
            driver: {
                type: "postgres",
                host: "host",
                port: 5432,
                username: "username",
                password: "password",
                database: "username"
            },
            entities: [
                Photo, PhotoMetadata, Author, Album
            ],
            autoSchemaSync: true,
        });

    }

}

export { DBConnectionManager };

After I created my connection I want to bind a connection into my container:

kernel.bind<Connection>('DefaultConnection').toConstantValue(getConnectionManager().get());

and then I want to inject it into another class:

import { injectable, inject } from 'inversify';
import { Connection, FindOptions } from "typeorm";
import { IGenericRepository, ObjectType } from '../index';


    @injectable()
    class GenericRepository<T> implements IGenericRepository<T> {

        private connection: Connection;
        private type: ObjectType<T>;

        constructor( @inject('DefaultConnection') connection: Connection) {
            this.connection = connection;
        }

So in my container configuration how can I bind DefaultConnection that needs to wait for CreateConnection I can do with async and wait but I'm wonder if there is a cleaner way to achive this in inversify


Solution

  • Inversify 2.0 includes support for asynchronous factories (AKA Providers)

    A provider allows you can to declare a provider as follows:

    container.bind<<DbClient>("DbClient").to(DbClientClass);
    
    container.bind<interfaces.Provider<DbClient>>("Provider<DbClient>")
             .toProvider<DbClient>((context) => {
                return () => {
                    return new Promise<DbClient>((resolve, reject) => {
    
                        // Create instance
                        let dbClient = context.container.get<DbClient>("DbClient");
    
                        // Open DB connection
                        dbClient.initialize("//connection_string")
                                .then(() => {
                                    resolve(dbClient);
                                })
                                .catch((e: Error) => {
                                    reject(e);
                                });
                    });
                };
            });
    

    Then you can inject and consume the provider. The only problem is that it requires two-step initialization: the constructor injection and the async getDb() method.

    class UserRepository { 
    
        private _db: DbClient;
        private _dbProvider: Provider<DbClient>;
    
        // STEP 1
        // Inject a provider of DbClient to the constructor
        public constructor(
            @inject("Provider<DbClient>") provider: Provider<DbClient>
        ) { 
            this._dbProvider = provider;
        }
    
        // STEP 2
        // Get a DB instance using a provider
        // Returns a cached DB instance if it has already been created
        private async getDb() {
            if (this._db) return this._db;
            this._db = await this._dbProvider();
            return Promise.resolve(this._db);
        }
    
        public async getUser(): Promise<Users[]>{
            let db = await this.getDb();
            return db.collections.user.get({});
        }
    
        public async deletetUser(id: number): Promise<boolean>{
            let db = await this.getDb();
            return db.collections.user.delete({ id: id });
        }
    
    }
    

    We are working on a new feature to simplify the injection of asynchronous values. This feature will be included in inversify 3.0:

    class UserRepository { 
    
        // STEP 1
        public constructor(
            @inject("Provider<DbClient>") private provider: Provider<DbClient>
        ) {}
    
        public async getUser(): Promise<Users[]>{
            // STEP 2: (No initialization method is required)
            let db = await this.provider.someFancyNameForProvideValue;
            return db.collections.user.get({});
        }
    }