Angular Multiple Inheritance (How to destroy a codebase)

This is not a tutorial; please don’t do this.

A Brief Primer on Inheritance

A common school of thought among programmers is to reduce repetition wherever possible. Usually, this is a good idea, as if I need to change something, I only need to change it in one place. We usually accomplish this using inheritance, like this simple example:

class Animal {
  name: string;
}

class Dog extends Animal {
  bark() {}
}

This way, both an instance of Animal and an instance of Dog have the name property, but Dogs also have the bark method. If we need to later add a property to both Animals and Dogs, we can just add it to Animal, and the property will also be applied to Dog.

Multiple Inheritance

What if, however, we wanted to have multiple parent classes for a class? Say we have two classes like so:

class FamilyMember {
  callHome() {}
}

class Animal {
  name: string;
}

We want to classify Dogs as both FamilyMembers and Animals, having them inherit properties and methods from both classes, so how would we do this? In other circumstances, we would modify either Animal or FamilyMember to inherit from the other, then we can make Dog inherit from whichever is the top-level class. However, we don’t want FamilyMember and Animal to overlap, so we can’t do this.

Another solution is to use interfaces instead of classes:

interface IFamilyMember {
   callHome();
}

class FamilyMember implements IFamilyMember {
  callHome() {}
}

interface IAnimal {
  name: string;
}

class Animal implements IAnimal {
  name: string;
}

class Dog implements IFamilyMember, IAnimal {
  name: string;
  callHome() {}
}

And this works fine. If we want to generically address family members in a variable, we can simply use the type IFamilyMember instead of FamilyMember, thus supporting both FamilyMember and Dog.

The problem comes in when you’re an obsessive perfectionist who will stop at nothing to eliminate all repetition possible, even if it completely ruins code quality. We now begin our quest for Multiple Inheritance!

Other, Better Languages

In C++, multiple inheritance can be achieved like so:

class A {}
class B {}
class C: public A, public B {}

A Terrible Hack

So what if we want this functionality in TypeScript? If it supported MI, it would probably look something like this:

class Dog extends Animal, FamilyMember {}

Let’s step it up a notch and define our real scenario: We want to build a robust form validation system, and define a component template that we can easily extend to prevent rewrites. This also needs to either implement an interface or extend a common parent for type-safe interaction with the validating directive that we’ll also use.

We then also want to define a similar parent component for one featuring a tabbed layout, and we need to define a component that is both a tab and validating. Let’s look at these individual pieces:

We now have our component, TestComponent that extends from ValidatingComponent, using the template of TestObject. If we call this.save from our TestComponent, it will call ValidatingComponent.save, as expected. However, we haven’t taken care of extending Tab as well. We need to create an intermediary class, ValidatingTabbedComponent, which we can then extend on many components to, by extension, inherit properties from both Validating and Tab.

To understand how this upcoming hack works, we first have to understand JavaScript Prototypes. If you haven’t heard of this before, this MDN article explains it pretty well. Essentially, at its core, every class in JavaScript is an object called a prototype, and that prototype can be freely modified to create programmatically-generated classes. We can also pass this prototype to a function and modify the class, returning a new class as a result.

Now a lot has changed here, so let’s unpack it step by step.


type BaseConstructor<T> = new(...args: any[]) => T;
class BasicClass { } 

These are simply here to define a “Class” and “Constructor” for our functions. Since classes in JavaScript are essentially defined by their constructor functions, BaseConstructor can be used to describe a class.


We need IValidatingComponent in order to maintain the inheritance history, as it is not preserved when modifying classes through functions.

ValidatingComponent has been changed to a function that returns a class. We accept two type arguments and one argument:

  • T is used for the type of the class we want to apply the Validating transformation to. We pass our child class (TestComponent) to Validating, and it returns TestComponent with the validation properties added on.
  • Y is the equivalent of our old T, used to denote the object being handled by the component. In this case, TestObject

ValidatingComponent has been turned into a function that returns a class. We use the BaseConstructor and BaseClass from earlier to represent empty shells, and we call the Validating function to add the necessary properties to the empty class. This is a structure we’ll be using often. If we want to create a new class that extends ValidatingComponent, we can write the following:

class Example extends ValidatingComponent<any>() {}

We won’t be using ValidatingComponent for our TestComponent, because we first need to apply the validation the Tab before it’s useful to us. But, we can still make non-tab Validating components using this method.


We don’t have to make any changes to Tab, as we’ve converted Validating into a function and can leave Tab as a class. If, however, you wanted to add more permutations, you could convert Tab to a function using the same process as used above.


Now, finally, we pass Tab to the Validating function to produce ValidatingTab and use it as the base class for TestComponent.

You’ll notice that ValidatingTab is, itself, a function. That’s because you can’t define a class in this manner with a template argument. That is to say, you couldn’t do this:

abstract class ValidatingTab<T> extends Validating<BaseConstructor<Tab<T>>, T>(Tab as BaseConstructor<Tab<T>>) implements IValidatingComponent {}

TypeScript will give you an error if you try.

Conclusion

The worst part about this is how well it works. This has been used in a production Angular app for some time now, and it hasn’t caused issues. JavaScript classes are easily modifiable, but I think it’s a testament to TypeScript that it’s actually able to provide accurate intellisense for A class extending from a function, which produces an empty class that extends from a function that takes a class as an argument, creating a class that extends from that argument and returning it.

Multiple Inheritance is already something you probably shouldn’t do. In other languages you can have serious problems if the two (or more) classes that you extend from share properties of like name but conflicting definitions. Ironically, in this bass-ackwards hacky approach we actually avoid any compiler errors by simply overriding any conflicts.

One of the drawbacks is the need to define interfaces for any of this to be useful in addressing these monstrous classes generically. In the example, I used IValidatingComponent as an interface on every returned class definition, and surprisingly enough it actually works. TestComponent does successfully implement IValidatingComponent, and it even works correctly with the template variable. At least in VS Code, this.data correctly reads as a TestObject from within TestComponent.

By far the biggest drawback comes with implementing template arguments to the classes. In doing so, we can’t simply create a base ValidatingComponent class by extending Validating(Tab). We have to create a function for each new definition. It still works, mind you, but it’s ugly.


I was motivated to write about this when I was approached by my coworker and asked to explain how this worked. I realized that I couldn’t, and I was the one who wrote this system in the first place.

The point is, you shouldn’t write code like this. It’s better to have it be a bit more verbose than impossible to decipher, relying on the weirdness of JavaScript function implementations instead of good common sense. I couldn’t even explain it when asked without having to thoroughly study it first.

You also shouldn’t do this because it’s not easily scalable. The number of function definitions (and the size of your class names) scale exponentially with the number of classes you need to have extend each other.

If you want to do it, go ahead. It was certainly a good learning experience for me, and I feel like I have a better grasp of how JavaScript has implemented objects than I did before. But don’t do it in a production app, or really anything that someone else will have to work on later.

It’s not worth it.

Leave a Reply

Your email address will not be published.