Monday, August 24, 2015

Angular and TypeScript: Inheritance and Dependency Injection Collide

My group does Angular and TypeScript.

TypeScript has chosen to mimic Java/C# style single inheritance. One of the effects of this is that if you have have something required in the constructor every base class has to be ready to pass that in, because it will call the constructor of it's parent.

Angular has dependency injection as a core mechanism. That means that every constructor essentially takes a list of external dependencies.

Combine these two things and you have a problem. Suppose you have a deep class hierarchy. Now, before I go further I want to say that in general I am not a big fan of deep class hierarchies and they are often a bad way to do things, but sometimes they make sense and TypeScript tends to steer you towards this kind of solution by doing things like making mixins a massive pain in the butt.

Okay, suppose you have a deep class hierarchy. You add a new dependency in your base class. Every single descendant class must now inject that dependency so it can pass it to it's ancestors. This is a pretty big violation of SOLID principles where you have one change affect many other classes and force them all to change.

My solution is to instead inject the "$injector". Parent classes can then do a call like "this.myService = $injector.get('myServiceName')". Things can still be mocked by manipulating the injector and you don't end up having to change the class signature of every inheriting class.

Now this potentially hinders Angular from finding dependency problems in your injection system, but right now I don't foresee this to be a significant problem.

EDIT: Apparently someone actually reads my blog reducing my ability to just make crap up....

export class ParentClass {
    constructor(public dependency1, 
                public dependency2, 
                public dependency3) {
        ...
    }
}

export class ChildClass extends ParentClass {
    constructor(public dependency1,
                public dependency2, 
                public dependency3, 
                public mydep1,
                public mydep2) {
        super(dependency1, dependency2, dependency3);
        ...
    }
}

The above code shows the problem. Your child class has to process the entire dependency list. So if you have a lot of inheritance you easily run afowl of the fragile base class problem. Any addition of a new dependency in the base class affects every descendant class.

export class ParentClass {
    public dependency1;
    public dependency2;
    public dependency3;

    constructor(public $injector) {
        this.dependency1 = $injector.get('dependency1');
        this.dependency2 = $injector.get('dependency2');
        this.dependency3 = $injector.get('dependency3');
    }
}

export class ChildClass extends ParentClass {
    constructor(public $injector,
                public mydep1,
                public mydep2) {
        super($injector);
        ...
    }
}

This code is structured so that changes to the parent class constructor will not force changes to all the children.

3 comments:

  1. Could you please provide sample code?

    ReplyDelete
    Replies
    1. Editted to have some sample code. Hope this helps.

      Delete
  2. This comment has been removed by the author.

    ReplyDelete