4

I have a typescript class, in which constructor I have a normal and an angular-injected argument:

export class MyClass {
    private translation:string;

    public static $inject = ['$filter'];
    constructor(name:string, $filter: ng.IFilterService) {
        this.translation = filter('translate')('code').toString();
    }
}

If I now want to create an object, how can I do it?

new MyClass('myname'); //won't compile because there are too few parameters
new MyClass('myname', filter); //makes no sense since I want to inject it

Even If I wrote $filter? it won't work because it won't recognize the scope and it will be undefined.

So, How can I get this to work?

My Approach

Let's say I am in another class, in which I want to create an object of MyClass. The following code will work but I don't like the idea of having to inject $filter in this class too, since it doesn't need it.

export class ClassUsingTheOtherClass {
    private filter:ng.IFilterService;

    public static $inject = ['$filter'];
    constructor($filter: ng.IFilterService) {
        this.filter = $filter;
    }

    doThings() {
        var clazz = new MyClass('myName', this.filter);
    }
}

I'd rather call something like this var clazz = new MyClass('myName'); and having $filter automatically injected as dependency in MyClass. Is this possible at all?

iberbeu
  • 15,295
  • 5
  • 27
  • 48
  • When would you ever call this `new MyClass('myname');`? It seems like it would never be called because you need a `filter` instance otherwise the class will eventually encounter an undefined error. What is the purpose of `myname` parameter? How would this ever be able to be injected? That definition would not work with Angular. – Igor Apr 04 '16 at 11:00
  • I rewrote the question. take a look. myname is just the string that is going to be translated using the angular-translate filter. And I don't want it to be injected because I want to pass it when I need to translate it. – iberbeu Apr 04 '16 at 11:31

2 Answers2

5

I think you are missing something about DI (injection). The point is to not create any dependencies manually. Instead always resolve them and with Angular this is done in the constructor automatically. The only exceptions should be with classes that have 0 angular dependencies and have limited to no behavior like a pure entity/data class.

Also Typescript affords you the ability to define interfaces (contracts). You should create them and use these as reference points instead of your class types directly. This will allow you to change behavior or even definitions (provides loose coupling). This also makes unit testing easier.

Your example could be rewriten like so:

export interface IMyService{
   doThing:()=>void;
}

// should be registered with angular with service name 'ngMyService'
export class MyClass implements IMyService {
    private translation:string;

    public static $inject = ['$filter'];
    constructor($filter: ng.IFilterService) {
        this.translation = $filter('translate')('code').toString();
    }

    doThing() : void {
        // something
    }
}

export class ClassUsingTheOtherClass {

    public static $inject = ['ngMyService'];
    constructor(private myService: IMyService) {
    }

    doThings() {
        this.myService.doThing();
    }
}

Now register MyClass with angular with the name ngMyService and register ClassUsingTheOtherClass with angular as well. Where you register them depends on their use (controller, service, factory, filter, etc).

When angular creates an instances of ClassUsingTheOtherClass then it automatically has MyClass injected into the constructor. MyClass in turn will automatically have $filter injected into the constructor.

Finally I see no purpose to the string name parameter in MyClass so I removed it. You will never be able to inject this because you cannot register a string with angular to be injected. You could either hard code the name in the type itself OR provide a name property / field that could be set by the user of MyClass (add the field to the interface IMyService) but this breaks the high cohesion rule in my opinion.

Igor
  • 60,821
  • 10
  • 100
  • 175
  • 1
    I like it. The problem was not really a general misunderstanding of DI but that I was not really using angular in the way I should. I was kind of trying to avoid registering every single thing in angular, but now I see it is the way to go. Anyway I think this is something that could be improved in Typescript – iberbeu Apr 04 '16 at 12:23
  • 1
    Good answer. As much focus on the why as the how – Dave Alperovich Apr 04 '16 at 22:20
  • @iberbeu - (*correction, I meant to write Good not God*) - Good deal! It took me a little bit too when I first started working with Angular and Typescript (I picked them both up at the same time). Once you get the hang of it though (defining dependencies via interfaces and using angular registration etc) it becomes something you almost don't have to think about anymore (2nd nature). – Igor Apr 04 '16 at 22:46
-2
export class MyClass {

    public static $inject = ['$scope'];
    constructor(name:string, $scope?: ng.IScope) {
        //do things...                
    }
}

By modding your class so there is a question mark behind $scope, you tell the compiler that the $scope is an optional parameter.

Now you can simply run new MyClass('myname');.

Pjetr
  • 1,372
  • 10
  • 20
  • Did you try that? It does compile as I already said in my question but scope will be undefined – iberbeu Mar 31 '16 at 13:05
  • 1
    I seem to have misread your question, I thought it was a problem to compile. I'm trying to make sense of your question, but I can't really know why you'd want to inject a scope in a generic class. If it needs a $scope, you should probably provide each class with his own $scope, that inherits from the $scope of the class where you generate the new instances. `new MyClass('myname', scope.$new(false));` – Pjetr Mar 31 '16 at 13:11
  • To write scope was a bad idea. What I really need is $filter in order to translate a thing. I rewrote my question. Is there another way to do this? Am I misunderstanding things here? – iberbeu Mar 31 '16 at 13:32