19

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.

1

6 Answers 6

23

The following is one way to achieve this:

class LogWithPrefixFactory {
    static LogService;
    constructor(prefix) {
        this.prefix = prefix;
    }

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

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


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

Rational: You effectively want a static property in a the LogWithPrefixFactory (using a closure in JS) , and you want it to come from Angular.

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

3 Comments

This is a great solution! but Kos gave a better way. because his solution was to keep the class looking the same as a regular class with injects, and not consts.
there are not constants here. Only static. Static just means that all instances of the class share the same non-instance reference (effectively identical to how a service works). The only difference here is that instead of a static injector returning static services shared by all instances, this has a static service without the injector.
I think this answer is better because it is a factory like you asked for.
14

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.

2 Comments

Yea, wrapping it with another function that get only the needed parameters is what I needed.
Why are these services and not factories?
8

I have achieved like below

module Dashboard {
    export class LayoutServiceFactory {
        static $inject = ["$q", "$http"];
        private q: ng.IQService;
        private http: ng.IHttpService;

        constructor(private $q: ng.IQService, private $http: ng.IHttpService) {
            this.q = $q;
            this.http = $http;
        }

        getDataFromServer(serviceUrl) {
            var deferred = this.q.defer();
            this.http.get(serviceUrl, null)
                .then(response => {

                    deferred.resolve((response) as any);

                });
            return deferred.promise;
        }

        static factory() {
            var instance = ($q: ng.IQService, $http: ng.IHttpService) =>
                new LayoutServiceFactory($q, $http);
            return instance;
        }
    }

    appModule.factory("LayoutService", LayoutServiceFactory.factory());
}

Comments

2

This worked for me.

    namespace Services
    {
      export class MyService
      {
        constructor( protected $someService :any )
        {
          return this;
        }
      }
    }
    angular.module( 'myModule', [] ).factory( Services );

Comments

1

this is how i do it

 namespace app {
             let app =angular.module('foo',[]);
               app.factory(factories);//for registering whatever is there in factories namespace

      }

 namespace app.factories {


 export class fooFactory {

    static $inject = ['fooHelperService']
    constructor(fooHelperService: services.fooHelperService) {

        return {

            fooFunc: () => {

               return 'hellow world'
            }
        }
    }
 }

}

1 Comment

I get: Return type of constructor signature must be assignable to the instance type of the class. Type fooFunc:() => string is not assignable to type 'fooFactory'. Object literal may only specify known properties, and fooFunc does not exist in type fooFactory
1

You can create a type that allows you to define what the constructor of the factory looks like:

// Defining the factory

// THIS IS THE IMPORTANT PART!!
export type SelectorFactory = new (config: any) => Selector;

export class Selector {

    constructor(protected config: any, protected $http: ng.IHttpService) {
        // do some stuff
    }
}

angular.module('app')
    .factory('Selector', ($http: ng.IHttpService) => {
        // This is what the factory looks like to the end user
        return (config: any) => {
            return new Selector(config, $http);
        };
    });

// Using the factory
export class SampleCtrl {
    constructor(public SelectorFactory: SelectorFactory) {
        let config = { op: 1 };

        let selector: Selector = new SelectorFactory(config);
    }
}

2 Comments

The error with this is: "error TS4081: Exported type alias 'SelectorFactory' has or is using private name 'Selector'
try changing class Selector to export class Selector maybe?

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.