gilamran gilamran - 6 months ago 84
AngularJS Question

How can I define an AngularJS factory using TypeScript class that has constructor parameters

I want to write a TypeScript class that gets a "prefix" parameter in the constructor, this class also needs access to a LogService inject.

Using plain JavaScript you should do it like this:

angular.module('myModule', []).factory('LogWithPrefixFactory', ['LogService', function(LogService) {
var LogWithPrefixFactory = function(prefix) {
this.prefix = prefix;
}

LogWithPrefixFactory.prototype.log = function(txt) {
// we have access to the injected LogService
LogService.log(this.prefix, txt);
}

return LogWithPrefixFactory;
}]);


So when you inject this factory to a controller, you can initiate it many times like this (No need to inject the LogService):

angular.module('myModule').controller('Ctrl', function(LogWithPrefixFactory) {
var foo = new LogWithPrefixFactory("My PREFIX");
var foo = new LogWithPrefixFactory("My OTHER PREFIX");
}


How would you define this Factory in a TypeScript class?
TypeScript classes can not be defined inside functions...
This class should have access to the LogService, but it can't get it in one of the injects.

Answer

There are at least 2 options.

First option, have LogWithPrefixFactory provide a method getInstance that returns the prefixed logger.

module services {
  class LogService {
    $window: any;

    constructor($window: any) {
      this.$window = $window;
    }

    log(prefix: string, txt: string) {
      this.$window.alert(prefix + ' :: ' + txt);
    }
  }
  angular.module('services').service('LogService', ['$window', LogService]);


  export interface ILog {
    log: (txt) => void;
  }

  export class LogWithPrefixFactory {
    logService: LogService;

    constructor(logService: LogService) {
      this.logService = logService;
    }

    getInstance(prefix: string): ILog {
      return {
        log: (txt: string) => this.logService.log(prefix, txt);
      }
    }
  }

  angular.module('services').service('LogWithPrefixFactory', ['LogService', services.LogWithPrefixFactory]);
}

Which can be used in the controller like:

this.log1 = logWithPrefixFactory.getInstance("prefix1");
this.log2 = logWithPrefixFactory.getInstance("prefix2");

Complete plunker here.

Second option (similar to another answer), give Angular another function to be used as a constructor, which handles manually the LogService constructor injection (personally, I don't like static).

angular.module('services').service('LogWithPrefixFactory', ['LogService', function(logService) {
    return function LogWithPrefixFactory(prefix) {
      return new LogWithPrefix(prefix, logService);
    };
}]);

Which can be used in the controller like:

this.log1 = new LogWithPrefixFactory("prefix1");
this.log2 = new LogWithPrefixFactory("prefix2");

or even:

this.log1 = LogWithPrefixFactory("prefix1");
this.log2 = LogWithPrefixFactory("prefix2");

LogWithPrefixFactory is injected in the controller but it's not the TypeScript class constructor, it's the intermediate function which returns the actual instance of the class, after it has been "manually" injected with LogService.

Complete plunker here.

Note: These plunkers synchronously compile typescript on the browser. I have tested it only on Chrome. No guarantees that they'll work. Finally, I manually added a small part of angular.d.ts. Full file was very big and my proxy does not allow large POSTs.

Comments