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.

Community
  • 1
  • 1
kkost
  • 374
  • 3
  • 14

2 Answers2

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.

basarat
  • 261,912
  • 58
  • 460
  • 511
  • Thanks! This seems like a perfect solution for my problem, and I'll certainly look into it when I can use it. – kkost Mar 30 '15 at 09:34
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.

Sly_cardinal
  • 12,270
  • 5
  • 49
  • 50
  • `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](http://www.typescriptlang.org/Playground#src=var%20r%3B%0D%0Ar.foo%20%3D%20%28req%3A%20express.Request%2C%20res%3A%20express.Response%29%20%3D%3E%20{%0D%0A%09res.send%28JSON.stringify%28{%20loc%3A%20%27FooController%3A%3Afoo%202%20here%20%27%2Breq.params.fooId%20}%29%29%3B%0D%0A}%0D%0Ar.foo.route%20%3D%20%27%2Ffoo%2F%3AfooId%27%3B%0D%0Ar.foo.route%20%3D%20%27get%27%3B%0D%0A). – Paleo Mar 29 '15 at 12:10
  • But I've +1ed the answer of Basarat, it looks nice for a solution with a class. – Paleo Mar 29 '15 at 12:11
  • 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. – Sly_cardinal Mar 29 '15 at 12:26
  • 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. – kkost Mar 31 '15 at 09:30