Search code examples
typescriptclassexpressthisexecutioncontext

TypeScript: "Outsourcing" methods. The clean way


I'm currently working on a class I am using as middleware in express. Before i go into the class keep in mind that i will be injecting the middleware later by first creating an instance of my Class "Authenticator" and then injecting it's method with

app.use(authInst.express)

So the key point will be the execution context (this) of this function. This is the code I've got so far

Alternative 1

class Authenticator {
    opts:IAuthOpts ;
    express: RequestHandler| ErrorRequestHandler

    constructor(opts:IAuthOpts){
        this.opts = opts;
        this.express = function(req:Request, res:Response, next:NextFunction){

            if(this.opts.enabled) {
                mainController(req, res, next, this.opts)
            } else {
                next();
            }
        }
    }
}

This is working. Yet I don't want to write the function in the constructor as I find it pretty ugly code. Putting the express method directly in the class like this

Not working

class Authenticator {
    opts:IAuthOpts;

    constructor(opts:IAuthOpts){
        this.opts = opts;
    }

    express(req, res, next){
        if(this.opts.enabled) {
            mainController(req, res, next, this.opts)
        } else {
            next();
        }    
    }
}

will not work as the execution context from express will give me this as undefined. So what's left is using this kind of alternative

Alternative 2

class Authenticator {
    opts:IAuthOpts ;
    express: RequestHandler| ErrorRequestHandler

    constructor(opts:IAuthOpts){
        this.opts = opts;
        this.express = _express.bind(this);
    }

    private _express(req, res, next){
        if(this.opts.enabled) {
            mainController(req, res, next, this.opts)
        } else {
            next();
        }    
    }
}

Now this works too and is my preferred solution for this problem so far, as it would also be easy to outsource the method to another file and keeping my files small. The downside is the bind. I am not a big fan of bind as I want my functions to return the same value if i invoke them with the same parameters no matter where they are called from and in this case you always need to bind the class to it.

Is there a better solution to outsource a method from a TypeScript class and still not having to inject the execution context with bind?


Solution

  • In place of the bind, you can use an arrow function:

    class Authenticator {
        opts:IAuthOpts ;
    
        constructor(opts:IAuthOpts){
            this.opts = opts;
        }
    
        express = (req, res, next) => {
            if(this.opts.enabled) {
                mainController(req, res, next, this.opts)
            } else {
                next();
            }    
        }
    }
    

    If you then wanted to move the implementation to another file, it would probably be clearest to define a function that takes the Authenticator as an ordinary parameter along with req, res, and next and then call that function from your arrow function:

    class Authenticator {
        opts:IAuthOpts ;
    
        constructor(opts:IAuthOpts){
            this.opts = opts;
        }
    
        express = (req, res, next) => otherFunction(this, req, res, next);
    }
    
    // In other file
    function otherFunction(authenticator: Authenticator, req: Request, res: Response, next: NextFunction) { 
        if(authenticator.opts.enabled) {
            mainController(req, res, next, authenticator.opts)
        } else {
            next();
        }    
    }
    

    If this wasn't what you were looking for, please clarify the question.