1

Are there any idioms in typescript to define properties to methods in a class inline with the method definition?

I'm looking for something similar to .NET attributes.

Here's an example of what I've got so far

class FooController {
    foo(req: express.Request, res: express.Response) {
        res.send(JSON.stringify({ loc: 'FooController::foo 2 here '+req.params.fooId }));
    }
    bar(req: express.Request, res: express.Response) {
        res.send(JSON.stringify({ loc: 'FooController::bar here' }));
    }
}
FooController.prototype.foo['route'] = '/foo/:fooId';
FooController.prototype.foo['verb'] = 'get';
FooController.prototype.bar['route'] = '/bar';
FooController.prototype.bar['verb'] = 'post';

where a different function will consume FooController and interrogate the method attributes to set up a routing table before any of the FooController methods are invoked.

I don't like the distance between my method definitions and my property definitions, especially as my methods get larger and supporting functions sit in between the two.

Is there anything better I can do here? If there are different language features I should be using to express this other than properties, I'm open to that. I'm especially interested if the solution retains type safety.

I did review build a function object with properties in typescript but I don't think the solutions in there are a good match because of the late binding and object method requirements.

I'm using typescript compiler version 1.0.3 with Visual Studio 2013 Update 3.

2 Answers 2

4

This is an ideal candidate for TypeScript Decorators : https://github.com/Microsoft/TypeScript/issues/2249. Your code refactored:

class FooController {
    @route(/*put your config here*/)
    foo(req: express.Request, res: express.Response) {
        res.send(JSON.stringify({ loc: 'FooController::foo 2 here '+req.params.fooId }));
    }
    @route(/*put your config here*/)
    bar(req: express.Request, res: express.Response) {
        res.send(JSON.stringify({ loc: 'FooController::bar here' }));
    }
}

Note that you will need TypeScript 1.5 which should be released very shortly.

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks! This seems like a perfect solution for my problem, and I'll certainly look into it when I can use it.
3

The easiest way to keep type safety on your route methods is to define an interface:

interface IRoute {
    (req: express.Request, res: express.Response): void;

    route?: string;
    verb?: string;
}

Then define your routing methods as properties that implement the IRoute interface. Then you can use the constructor to define the additional route and verb properties:

class FooController {

    constructor(){
        // Type-safety on these properties:
        this.foo.route = '/foo/:fooId';
        this.foo.verb = 'get'

        this.bar.route = '/bar';
        this.bar.verb = 'post';
    }

    // Use fat-arrow syntax to bind the scope to this instance.
    foo: IRoute = (req: express.Request, res: express.Response) => {
        this.bar(req, res);
    }

    bar: IRoute = (req: express.Request, res: express.Response) => {
        // TypeScript knows about the additional properties on `this.foo`.
        this.foo.route;

        res.send(JSON.stringify({ loc: 'FooController::bar here' }));
    }

}

You might still have a bit of "distance" between the constructor and the route methods, but the benefits are:

  • You gain type-safety
  • You don't have to fiddle around with the FooController.prototype['foo']...
  • It's all self-contained in the class definition

Here's the above example in the TypeScript playground.

4 Comments

this.foo.route = ... in the constructor means each instance of the class will redefine the routes in the prototype. It's not elegant. A pure JavaScript solution, without classes, is better.
But I've +1ed the answer of Basarat, it looks nice for a solution with a class.
Thanks, I'd considered that, but decided to go with a straight translation of kkost's example. I figure these types of route/controller classes aren't instantiated very often so having the clarity of a self-contained class outweighs the route redefinition in the constructor.
Thanks - marked as accepted. What I do like about it is the better type safety and encapsulating the concerns in the class definition. I would like it better if I could interrogate properties before I instanced an object (my pattern is establish route from method properties, and then fulfill route with a new controller object) and if the properties could be established in the exact same space as the method. I think TypeScript Decorators (mentioned by @basarat) will get me there when they are available to me.

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.