TDaver
TDaver

Reputation: 7264

Typescript decorators as mixins?

I'm writing a big application in Angular2. I have a base class that does this:

export abstract class ReactiveComponent implements OnInit, OnDestroy, AfterViewInit {
    abstract ngOnInit(): void;
    protected _subscriptions: Rx.Subscription[] = [];
    private __viewInit = new Rx.Subject<void>();
    protected _viewInit: Rx.Observable<void> = this.__viewInit;
    ngOnDestroy() {
        if (this._subscriptions)
            this._subscriptions.forEach(s => s.unsubscribe());
    }
    ngAfterViewInit() {
        this.__viewInit.complete();

    }
}

But some of my classes need to have other base classes, and I'm hitting the classic mixin/inheritance brick wall.

So I was thinking about turning this into a decorator, since Angular2 won't care where those methods came from, I can easily do a constructor.prototype.ngOnDestroy = ...

Right?

Which leaves me with 2 questions:

  1. Do I have any way of letting the protected members "show up" in the decorated class so that I can write this._subscriptions in Typescript?
  2. What if I want to add a @ContentChildren(...) someStuff; property via a mixin? How can I dynamically define a decorator on constructor.prototype.someStuff? Am I in the wrong ballpark for "traits" all together?

Upvotes: 2

Views: 2234

Answers (1)

Nitzan Tomer
Nitzan Tomer

Reputation: 164277

(1) No, you probably can't enjoy both using mixins and having protected members/methods.
While the mixins are actual classes, you inherit them as interfaces:

class MixinA {
    public a(): void { console.log("A"); }
}

class MixinB {
    public b(): void { console.log("B"); }
}

class MyClass implements MixinA, MixinB {
    public a: () => void;
    public b: () => void;
}

applyMixins(MyClass, [MixinA, MixinB]);

Where the key word is implements.
As a general OO principle interfaces are public contracts and they don't deal with the actual implementation which is the realm of protected functionality.

(2) If I understand you correctly when you say "dynamically define a decorator" then yes, it's similar to the example for Property Decorators in the official docs, so you should be able to do something like (untested):

const numbersMetadataKey = Symbol("numbers");
type Producer = () => number;
type Reducer = (previous: number, current: number) => number;

function numbers(...props: Array<number | Producer>) {
    return Reflect.metadata(numbersMetadataKey, props);
}

function getNumbers(target: MyClass, propertyKey: string) {
    let arr: Array<number | Producer> = Reflect.getMetadata(numbersMetadataKey, target, propertyKey);
    return arr.map(item => typeof item === "number" ? item : item());
}

function numberFactory(min: number = 0, max: number = 100): number {
    return Math.random() * (max - min) + min;
}

class MyClass {
    @numbers(numberFactory(), numberFactory.bind(null, 50, 200))
    private reducer: Reducer;

    constructor(reducer: Reducer) {
        this.reducer = reducer;
    }

    reduce(): number {
        let numbers: number[] = getNumbers(this, "reducer");
        return numbers.reduce(this.reducer);
    }
}

new MyClass((previous, current) => previous + current).reduce();
new MyClass((previous, current) => previous * current).reduce();

I just want to add that sometimes multiple inheritance is required and unavoidable, if supported at all (here it's implemented with mixins) but in other times the need for it just points to an inadequate design.

Upvotes: 1

Related Questions