Skip to content

Instantly share code, notes, and snippets.

@starbeast
Last active June 5, 2017 20:34
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save starbeast/a790fcd5a8db7c90b62d5b3cb19d653c to your computer and use it in GitHub Desktop.
Save starbeast/a790fcd5a8db7c90b62d5b3cb19d653c to your computer and use it in GitHub Desktop.
Mixins + DI
import {
Injectable,
Inject,
OpaqueToken
} from '@angular/core';
let resolveParameters = function (target: any, params: any[]) {
if(params) {
params.forEach((param, index) => {
// this code is needed to make @Inject work
if (param && param.annotation && param.annotation instanceof Inject) {
param(target, void 0, index);
}
});
}
return Reflect.getMetadata('parameters', target);
};
let getParamTypesAndParameters = function(target, paramTypes, rawParameters) {
var parameters = Array(paramTypes.length).fill(null);
if(rawParameters) {
rawParameters.slice(0, paramTypes.length).forEach((el, i) => {
parameters[i] = el;
});
}
return [paramTypes, parameters];
};
function MixinInjectable(...paramTypes: any[]) {
return function (childTarget: any) {
let parentTarget = Object.getPrototypeOf(childTarget.prototype).constructor;
let [parentParamTypes, parentParameters] = getParamTypesAndParameters(parentTarget,
Reflect.getMetadata('design:paramtypes', parentTarget),
Reflect.getMetadata('parameters', parentTarget)
);
let [childParamTypes, childParameters] = getParamTypesAndParameters(childTarget,
paramTypes || [],
resolveParameters(childTarget, paramTypes)
);
Reflect.defineMetadata('design:paramtypes', childParamTypes.concat(parentParamTypes), childTarget);
Reflect.defineMetadata('parameters', childParameters.concat(parentParameters), childTarget);
Reflect.defineMetadata('parentParameters', parentParameters, childTarget);
}
}
@Injectable()
class Dependency {
foo(): void {
console.log('foo');
}
}
@Injectable()
class AnotherDependency {
bar(): void {
console.log('bar');
}
}
interface IYetAnotherDependency {
baz(): void;
}
@Injectable()
class YetAnotherDependency implements IYetAnotherDependency {
baz(): void {
console.log('baz');
}
}
const YetAnotherDependencyToken = new OpaqueToken('yet-another-dependency');
interface IBehavior {
foobar(): void;
}
interface IAnotherBehavior {
foobaz(): void;
}
interface IRestriction {
restriction(): void;
}
type Constructor<T> = new(...args: any[]) => T;
export function FooBarable<T extends Constructor<IRestriction>>(Base: T) {
@MixinInjectable(Dependency, AnotherDependency)
class Mixin extends Base implements IBehavior {
/*
we have to be careful with property names as TS will complain
if multiple classes in the inheritance chain has private properties
with the same name.
*/
private dependency1: Dependency;
private anotherDependency: AnotherDependency;
foobar(): void {
this.restriction();
this.dependency1.foo();
this.anotherDependency.bar();
}
constructor(...args: any[]) {
super(...args.slice(2));
this.dependency1 = args[0];
this.anotherDependency = args[1];
}
}
return Mixin;
}
export function FooBazable<T extends Constructor<IRestriction>>(Base: T) {
@MixinInjectable(Dependency, Inject(YetAnotherDependencyToken))
class Mixin extends Base implements IAnotherBehavior {
private dependency2: Dependency;
private yetAnotherDependency: IYetAnotherDependency;
foobaz(): void {
this.restriction();
this.dependency2.foo();
this.yetAnotherDependency.baz();
}
constructor(...args: any[]) {
super(...args.slice(2));
this.dependency2 = args[0];
this.yetAnotherDependency = args[1];
}
}
return Mixin;
}
@Injectable()
export class ClassDependency {
call(): void {
console.log('class dependency');
}
}
@Injectable()
class WithoutMixins implements IRestriction {
constructor(private classDependency: ClassDependency) {
}
restriction() {
this.classDependency.call();
}
}
@MixinInjectable()
export class WithMixins extends FooBazable(FooBarable(WithoutMixins)) implements IRestriction, IBehavior, IAnotherBehavior {
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment