yankee
yankee

Reputation: 40740

Can I create a TypeScript class within a function and refer to its parameters?

E.g. in angularJS I may use the following construction:

myApp.factory('MyFactory', function(injectable) {
    return function(param) {
        this.saySomething = function() {
            alert("Param=" + param + " injectable=" +injectable);
        }
    };
});

This can later be used like this:

function(MyFactory) {
    new MyFactory().saySomething();
}

When the function passed to the method factory gets invoked, the param injectable is caged and will further be available to new instances of MyFactory without any need to specify that parameter again.

Now I want to use TypeScript and obviously I want to specify that my MyFactory is newable, and has a function saySomething. How could I do this elegantly?

I could write something like this:

class MyFactory {
    constructor(private injectable, private param) {}
    saySomething() {
        alert(...);
    }
}
myApp.factory('myFactory', function(injectable) {
    return function(param) {
        return new MyFactory(injectable, param);
    }
});

But this changes the API:

function(myFactory) {
    myFactory().saySomething();
}

I wonder if it could be more elegant, because I like how the "new" expresses quite clearly that a new unique object is created and this object creation is the whole purpose of the factory.

Upvotes: 3

Views: 4726

Answers (3)

tanguy_k
tanguy_k

Reputation: 12283

** Edit: TypeScript >= 1.6 supports class expressions and you can now write things like:

myApp.factory(injectable: SomeService) {
  class TodoItem {
    ...
  }
}

** Original answer:

I have the same problem: with AngularJS and ES5, I enjoy dependency injection not polluting constructors and be able to use the new keyword.

With ES6 you can wrap a class inside a function, this is not yet supported by TypeScript (see https://github.com/Microsoft/TypeScript/issues/307).

Here what I do (MyFactory is now class TodoItem from a todo app to be more relevant):

class TodoItem {
  title: string;
  completed: boolean;
  date: Date;

  constructor(private injectable: SomeService) { }

  doSomething() {
    alert(this.injectable);
  }
}

class TodoItemFactory() {
  constructor(private injectable: SomeService) { }

  create(): TodoItem {
    return new TodoItem(this.injectable);
  }

  // JSON from the server
  createFromJson(data: any): TodoItem {
    var todoItem = new TodoItem(this.injectable);
    todoItem.title = data.title;
    todoItem.completed = data.completed;
    todoItem.date = data.date;
    return todoItem;
  }
}

// In ES5: myApp.factory('TodoItem', function(injectable) { ... });
myApp.service('TodoItemFactory', TodoItemFactory);


class TodosCtrl {
  // In ES5: myApp.controller('TodosCtrl', function(TodoItem) { ... });
  constructor(private todoItemFactory: TodoItemFactory) { }

  doSomething() {
    // In ES5: var todoItem1 = new TodoItem();
    var todoItem1 = this.todoItemFactory.create();

    // In ES5: var todoItem2 = TodoItem.createFromJson(...)
    var todoItem2 = this.todoItemFactory.createFromJson(
      {title: "Meet with Alex", completed: false}
    );
  }
}

This is less elegant than with ES5 and functions (and not using classes with TypeScript is a no go) :-/

What I would like to write instead:

@Factory
@InjectServices(injectable: SomeService, ...)
class TodoItem {
  title: string;
  completed: boolean;
  date: Date;

  // No DI pollution
  constructor() { }

  saySomething() {
    alert(this.injectable);
  }

  static createFromJson(data: string): TodoItem {
    ...
  }
}

@Controller
@InjectFactories(TodoItem: TodoItem, ...)
class TodosCtrl {
  constructor() { }

  doSomething() {
    var todoItem1 = new TodoItem();

    var todoItem2 = TodoItem.createFromJson({title: "Meet with Alex"});
  }
}

Or with functions:

myApp.factory(injectable: SomeService) {
  class TodoItem {
    title: string;
    completed: boolean;
    date: Date;

    // No constructor pollution
    constructor() { }

    saySomething() {
      alert(injectable);
    }

    static createFromJson(data: string): TodoItem {
      ...
    }
  }
}

myApp.controller(TodoItem: TodoItem) {
  class TodosCtrl {
    constructor() { }

    doSomething() {
      var todoItem1 = new TodoItem();

      var todoItem2 = TodoItem.createFromJson({title: "Meet with Alex"});
    }
  }
}

Upvotes: 1

bingles
bingles

Reputation: 12183

What's the reason for instantiating multiple instances of MyFactory? Would you not want a single instance of your factory to be injected into your dependent code?

I think using the class declaration you provided will actually look like this once injected:

function(myFactory) {
    myFactory.saySomething();
}

If you are really needing to pass a constructor function into your dependent code, then I think you will have to ditch TypeScript classes, since they can't be defined inside of a function which means you would have no way to create a closure on a variable injected into such function.

You do always have the option of just using a function in TypeScript instead of a class. Still get the strong typing benefits and can call 'new' on it since it is still a .js function at the end of the day. Here's a slightly more TypeScriptiffied version:

myApp.factory('MyFactory', (injectable: ng.SomeService) => {

    return (param: string) => {

        return {
            saySomething: () {
                alert("Param=" + param + " injectable=" +injectable);
            }
        };
    };
});

Upvotes: 0

basarat
basarat

Reputation: 276171

I could write something like this

This is what I do

Can I create a TypeScript class within a function

No it needs to be at the top level of the file or in a module. Just FYI if were able to create it inside a function the information would be locked inside that function and at least the type info would be useless.

Upvotes: 0

Related Questions