inversify / InversifyJS

A powerful and lightweight inversion of control container for JavaScript & Node.js apps powered by TypeScript.
http://inversify.io/
MIT License
11.16k stars 713 forks source link

TypeError: Cannot read property Property of undefined #1179

Open MarcoLeko opened 4 years ago

MarcoLeko commented 4 years ago

General Note on my tech-stack: I have an application composed of Node.js - Express- Inversifyjs - Typescript

I have a modular structure on my express-routes on seperate files. Now when I try to inject my database-connector class on one of the routes classes, I get the error: TypeError: Cannot read property 'mongoDBClient' of undefined

Expected Behavior

My expected behaviour should be to normally inject a class into other classes, if i take into consideration that my inversify-config is setted properly.

Current Behavior

Inversify does not inject my class properly. Node detects injected class as undefined.

Steps to Reproduce (for bug)

inversify config

import 'reflect-metadata';
...

const dependencyContainer = new Container();
dependencyContainer.bind<Object>(TYPES.ENVIRONMENTAL_CONFIG).toFactory(
    () => (context: interfaces.Context) => context ? CONFIG_DEVELOPMENT : CONFIG_PRODUCTION);
dependencyContainer.bind<AbstractRoutes>(TYPES.ABSTRACT_ROUTES).to(AuthRoutes).inTransientScope();
dependencyContainer.bind<AbstractRoutes>(TYPES.ABSTRACT_ROUTES).to(ApiRoutes).inTransientScope();
dependencyContainer.bind<MongoDBClient>(TYPES.MONGO_DB_CLIENT).to(MongoDBClient).inSingletonScope();
dependencyContainer.bind<CredentialHelper>(TYPES.HASH_GENERATOR).to(CredentialHelper).inSingletonScope();
dependencyContainer.bind<EmailCreator>(TYPES.EMAIL_CREATOR).to(EmailCreator).inSingletonScope();
dependencyContainer.bind<Express>(TYPES.EXPRESS).to(Express).inSingletonScope();

export default dependencyContainer;

entry-point file

import dependencyContainer from './di-config/inversify.config';
import MongoDBClient from './modules/db/mongo-db-client';
import Express from './modules/server/express';
import {TYPES} from './di-config/types';

const app = dependencyContainer.get<Express>(TYPES.EXPRESS);

(async () => app.start())();

express.file

@injectable()
export default class Express {

    private static readonly PORT: any = process.env.PORT;
    public app: express.Application;
    public server: Http.Server;
    private MongoStore = connectStore(session);
    private readonly environmentalProps: any;

    constructor(
        @inject(TYPES.MONGO_DB_CLIENT) private mongoDBClient: MongoDBClient,
        @inject(TYPES.ENVIRONMENTAL_CONFIG) private environmentFactory: Function,
        @multiInject(TYPES.ABSTRACT_ROUTES) private routeManager: AbstractRoutes[]
    ) {
        this.app = express();
        this.server = new Http.Server(this.app);
        this.environmentalProps = this.environmentFactory(isProduction);
    }

    public start() {
        return this.mongoDBClient.connect()
            .then(() => this.initServer())
            .then(console.log);
    }

    private async initServer() {
        this.createMiddleware();
        this.assignRouteEndpoints();
        return new Promise((resolve) => this.server.listen(Express.PORT, () => resolve(`Server listens on Port ${Express.PORT}`)));
    }

    private createMiddleware() {
        ...
        this.app.use(express.static(joinDir(this.environmentalProps.PATH_TO_STATIC_FILES)));
    }

    private assignRouteEndpoints() {
        this.routeManager.map((route: AbstractRoutes) => this.app.use(route.ROUTE_PARAMS, route.getRoutes()));
    }
}

abstract route class

@injectable()
export default abstract class AbstractRoutes {
    abstract ROUTE_PARAMS: string;
    public router = express.Router();

    abstract createEndpoints(): void;

    public getRoutes() {
        this.createEndpoints();
        return this.router;
    }
}

one of the route files

Error is thrown here

@injectable()
export default class AuthRoutes extends AbstractRoutes {

    public ROUTE_PARAMS: string = '/auth';

    constructor(@inject(TYPES.MONGO_DB_CLIENT) public mongoDBClient: MongoDBClient,
                @inject(TYPES.EMAIL_CREATOR) public emailCreator: EmailCreator) {
        super();
        console.log(mongoDBClient); // <== is defined
        console.log(mongoDBClient.connectionManager); // <== some property is undefined
    }

    public async checkLoggedIn(request: any, response: any) {
        const sessionId = request.cookies.sid;
        const uid = request.session?.user?.uid;
        console.log(this.mongoDBClient); // <== crashes here
        response.status(200).send({foo: 'bar'})
    }

Context

The error is thrown in my route file in the checkdLogggedIn method. Normally i should be able to import the mongoDBClient class. Be aware that i import the mongoDBClient also in the parent Express class.

Your Environment

Versions:

Stack trace

(node:7480) UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'mongoDBClient' of undefined
    at /home/marco/WebstormProjects/help-educate/backend/src/modules/server/routes/auth-routes.ts:24:26
    at step (/home/marco/WebstormProjects/help-educate/backend/src/modules/server/routes/auth-routes.ts:58:23)
    at Object.next (/home/marco/WebstormProjects/help-educate/backend/src/modules/server/routes/auth-routes.ts:39:53)
    at /home/marco/WebstormProjects/help-educate/backend/src/modules/server/routes/auth-routes.ts:33:71
    at new Promise (<anonymous>)
    at __awaiter (/home/marco/WebstormProjects/help-educate/backend/src/modules/server/routes/auth-routes.ts:29:12)
    at AuthRoutes.checkLoggedIn (/home/marco/WebstormProjects/help-educate/backend/src/modules/server/routes/auth-routes.ts:86:16)
    at Layer.handle [as handle_request] (/home/marco/WebstormProjects/help-educate/backend/node_modules/express/lib/router/layer.js:95:5)
    at next (/home/marco/WebstormProjects/help-educate/backend/node_modules/express/lib/router/route.js:137:13)
    at Route.dispatch (/home/marco/WebstormProjects/help-educate/backend/node_modules/express/lib/router/route.js:112:3)
(node:7480) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
(node:7480) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
akisiel1 commented 4 years ago

@MarcoLeko have you found a solution?

dmarov commented 2 years ago

I had similar issue. Resolved after putting TYPES in it's own file https://github.com/inversify/InversifyJS/issues/1455