personal blog on code


Having fun with Mixins in Angular

The Good, the Bad, the Ugly

In my last post I wrote about Statemanagement in Angular. At the end I wrote about a Connect Mixin I had in my mind at that time. Okay to be honest I had a Higher Order Component in mind, but that’s not so easy to achieve in Angular. So let’s take one step after the other. The purpose of the Connect Mixin would be to connect to a NGRX Store and provide us with data from it. Finally it made it into this writeup. If you don’t read the whole post I would encourage you to skip to the Connect-Mixin part. Or at least read the last part: Are Mixins bad?

1️. What are Mixins?

MixIn programming is a style of software development where units of functionality are created in a class and then mixed in with other classes. A mixin class is a parent class that is inherited from — but not as a means of specialization. Typically, the mixin will export services to a child class, but no semantics will be implied about the child “being a kind of” the parent — 1

In my own words: Mixins are a special kind of class inheritance. They give you the power of multiple inheritance and allow you to use code from many different classes. But why?

2️. Why Mixins? 🤷‍

They enable us to share cross functional behaviour. Say we have some generic functionality to connect to our Redux store and some shared functionality that we want to share with many views. A naive approach could look like the following snippet:

class Connect {
  connect(selectors, actions) {
    //..
  }
}

class View {
  viewMode: 'create' | 'show' | 'edit';
}

class CommitView extends View, Connect { // [ts] Classes can only extend a single class. [1174]

} 

But the compiler is not quite happy with this. An error is thrown at us: Classes can only extend a single class!

How can we solve this problem? Mixins to the rescue 🚑. Let’s directly dive into some code. To achieve the desired behaviour from above we have to create some functions that mix in (MixIn^^) the behaviour. Such a Mixin function expects at least one argument, namely the class we wanna extend. And thats exactly what is happening inside the function. It’s returning a Class Expression that extends from the given class and adds additional behaviour on top of it:

class CommitView {}

// Mixin Functions
function mixinView<B extends Constructor>(Base: B) {
  return class extends Base {
    // every view has a viewMode
    viewMode: 'create' | 'show' | 'edit';
  }
}

function mixinConnect<B extends Constructor>(Base: B) {
  return class extends Base {

    constructor(...args: any []) {
      super(...args);
      // Connect to a specific store implementation
      this.connect({}, {});
    }

    connect(selectors, actions) {
      // TBD
      // 1. Read values with selectors
      // 2. Bind Actions to the Redux Dispatch function and the result to the Base class
    }
  }
}

// Usage:
// Enhance the base class CommitView with multiple behaviours
const ConnectedView = mixinConnect(mixinView(CommitView));

const view = new ConnectedView();
// access to the connect method provided by the Connect-Mixin and
// access to the viewMode provided by the View-Mixin
view.connect({}, {});
view.viewMode;

Now we can enhance our base class CommitView with the mixinConnect behaviour and the mixinView behaviour. I don’t wanna go into great detail about Mixins itself, because there is already a great introduction by Marius Schulz.

Basically a Mixin function takes a class definition and enhances it by extending from it. In my example above the mixinView adds the viewMode field and the mixinConnect connects to a specific NgRx store. For the complete implementation of the Connect-Mixin please scroll down to 4️⃣.

3. Mixins in Angular Material

While looking through some Angular Material source I noticed that there are Mixins used too. This was kind of a surprise, but it makes perfectly sense. They have many shared functionality across the different directives like a Color Mixin, Disabled Mixin, Tabindex Mixin, Error Sate Mixin or Initialized Mixin. Building multiple base classes with all different variants of behaviour would be simply not beneficial and maintainable. I think this is a perfect example of a valid usage of Mixins.

Have a look at the Color Mixin:

export function mixinColor<T extends Constructor<HasElementRef>>(
    base: T, defaultColor?: ThemePalette): CanColorCtor & T {
  return class extends base {
    private _color: ThemePalette;

    get color(): ThemePalette { return this._color; }
    set color(value: ThemePalette) {
      const colorPalette = value || defaultColor;

      if (colorPalette !== this._color) {
        if (this._color) {
          this._elementRef.nativeElement.classList.remove(`mat-${this._color}`);
        }
        if (colorPalette) {
          this._elementRef.nativeElement.classList.add(`mat-${colorPalette}`);
        }

        this._color = colorPalette;
      }
    }

    constructor(...args: any[]) {
      super(...args);

      // Set the default color that can be specified from the mixin.
      this.color = defaultColor;
    }
  };
}

Before the introduction of Mixins in Angular Material, the same code was copied and pasted again and again over multiple components.

4️. The Connect Mixin

As promised above, here comes the Connect Mixin. Its task is to work similar to the Connect Higher Order Component from React Redux. It allows you to specify selectors and actions that are then mixin’d to the given Component. In this mini sample I’v created and connected a Counter Component to an NgRx store.

alt text

Let’s take a look at of the usage of the Connect Mixin ⬇. Notice that it shows a very thin implementation of a container component. The container serves exactly one purpose: Connecting the counter component. That’s a different yet clean approach to container components. Why different? In Angular Applications I often see very big container components that connect all kind of data from the store and do additional business logic on them. They fail the Single Responsibility Principle in some way.

export class CounterViewBase {
  constructor(public injector: Injector) {}
}

export const CounterViewMixins = mixinConnect(CounterViewBase, 
  select => ({ 
    counter: select(counterSelector) 
  }), 
  dispatch => ({ 
    increment: (payload: number) => dispatch(incrementActionCreator(payload)),
    decrement: (payload: number) => dispatch(decrementActionCreator(payload)),
    reset: (payload: number) => dispatch(resetActionCreator(payload))
  }));

@Component({
  selector: 'counter-view',
  templateUrl: 'counter.container.html',
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class CounterContainer extends CounterViewMixins {
  constructor(public injector: Injector) {
    super(injector);
  }
}

In order to be able to use the dependency injection of Angular we have to add the class CounterViewBase in between and extend the container from the created Mixins class. This is because a Mixin classes must have a constructor with a single rest parameter of the type any[] and so we can’t inject the Injector directly into the Mixin.

In the template below we directly call the methods that got bound on the container through the Connect-Mixin. These methods are accessible through the vm object. This is a limitation we have to take in order to stay AOT compatible. You could name it whatever you like though. Important is only that the variable is known by the template at compile time.

<!-- counter.container.html -->
<ng-container *let="let count=counter from { 
    counter: vm.counter | async
  }">
  <counter 
    [count]="count"
    (onIncrement)="vm.increment(count+1)"
    (onDecrement)="vm.decrement(count-1)"
    (onReset)="vm.reset(0)">
  </counter>
</ng-container>

The Connect-Mixin itself expects a component that has an injector, therefore we’v created the HasInjector interface. We need the Injector to get the NgRx Store at runtime. We could have injected the NgRx Store directly in the container but I wanted to decouple the container from the store implementation we are using.

import {Injector} from '@angular/core';
import {Action, Store} from '@ngrx/store';

export function mixinConnect<T extends Constructor<HasInjector>, I, O>(base: T, inputs: Inputs<I>, outputs: Outputs<O>): T & Constructor<HasViewModel<I & O>> {
  return class extends base {

      vm: I & O;

      store: Store<any>;

      constructor(...args: any[]) {
          super(...args);

          this.store = this.injector.get(Store);

          // Bind inputs
          const selectedInputs: I = inputs(this.store.select.bind(this.store));

          // Bind outputs
          const boundOutputs: O = outputs(this.store.dispatch.bind(this.store));

          this.vm = Object.assign({}, boundOutputs, selectedInputs);
      }
  };
}

/* ––––––––––––––––––––––––––––––– */
/* ––     Types & Interfaces   ––– */
/* ––––––––––––––––––––––––––––––– */
export type Constructor<T> = new(...args: any[]) => T;

export interface HasInjector {
  injector: Injector;
}

export interface HasViewModel<T> {
  vm: T;
} 

const store: Store<any> = null;
type DispathFn = typeof store.dispatch;
type SelectFn = typeof store.select;

type Outputs<O> = (dispatch: DispathFn) => O; 
type Inputs<I> = (select: SelectFn) => I;

/* ––––––––––––––––––––––––––––––– */
/* ––     NGRX Typings         ––– */
/* ––––––––––––––––––––––––––––––– */
export interface Payload<T> { 
  payload: T; 
};

export type ActionCreator<T> = (payload: T) => Action & Payload<T>;

Although I’m not using the vm object in the container component in this example, the types Inputs and Outputs would give us a type-safe handling of it.

5️⃣Are Mixins bad?

I have to admit that I was a favor composition over inheritance evangelist until I made this writeup. I tried to avoid inheritance, especially multiple inheritance, at all cost. But the world is not just black or white, and for component libraries like Angular Material using multiple inheritance via Mixins does make sense.

I would not overuse Mixins though.

If you need much more dynamic behaviour in your components I would strive for “Composition over Inheritance”. Composition allows for interchangeable behaviour at runtime and adheres to the Open closed principle.

Still in line Next post will be on the topic of Mixins vs Higher Order Components. If I’ll keep up my pace it will be finished in June 2019. Just kidding 🐢

Checkout out the full source of the example on Github

🍀 💚 All the best for 2019 💚 🍀

Follow me on Twitter. I gratefully follow back.