Skip to content

Instantly share code, notes, and snippets.

@ovcharik
Last active September 21, 2018 11:20
Show Gist options
  • Save ovcharik/5cfdfd627fee366d3e37e39e8fdce717 to your computer and use it in GitHub Desktop.
Save ovcharik/5cfdfd627fee366d3e37e39e8fdce717 to your computer and use it in GitHub Desktop.
Декораторы для создания тонкой прослойки между AngularJS и произвольными классами TypeScript

Классы в рамках AngularJS

Ограничение

При наследование не корректно будет работать оператор instanceof

@Contextable("module") class A { }
@Contextable("module") class B extends A {}

const b = new B()
b instanceof B // true

// тут работать не будет
b instanceof A // false

Описываем класс, который при создании подключится к контексту ангуляра. Это позволит использовать ангуляровский DI в рамках объекта класса.

// foo.class.ts
@Contextable("myModule")
export default class Foo {
  @Inject() $window;

  public foo;
  public get override() {
    return "Foo";
  }

  constructor(public options) {
    this.foo = { foo: 'foo' };
  }
}

От него можно наследоваться. И вообще нет никаких ограничений по использованию.

// bar.class.ts
import Foo from './foo.class';

@Contextable("myModule")
export default class Bar extends Foo {
  @Inject("$document") $doc;

  public bar;
  public get override() {
    return "Bar";
  }

  constructor(public options) {
    super(options);
    this.bar = { ...this.foo, bar: 'bar' };
  }
}

Используем как-то так:

// usage.ts
import Bar from "./bar.class";

export class MyController {
  constructor() {
    const bar = new Bar({ msg: "Hello, world!" });
    // bar.$window;
    // bar.$document;
  }
}

export default angular
  .module("myModule", ["ng"])
  .controller("MyController", MyController);
export const INJECT_PROPERTY = "__contextableInject__";
export const Inject = (ngDependencyName?: string): PropertyDecorator => (
target: any,
property
) => {
const ctor = target.constructor;
ctor[INJECT_PROPERTY] = ctor[INJECT_PROPERTY] || [];
ctor[INJECT_PROPERTY].push({
property: property,
dependency: ngDependencyName || property
});
};
export const Contextable = (ngModuleName: string): ClassDecorator => {
let $injector: ng.auto.IInjectorService;
angular.module(ngModuleName).run([
"$injector",
(...dependencies) => {
[$injector] = dependencies;
}
]);
return ctor => {
const descriptors = () => {
const require: any[] = ctor.hasOwnProperty(INJECT_PROPERTY)
? ctor[INJECT_PROPERTY]
: [];
return require.reduce((acc, { dependency, property }) => {
acc[property] = {
value: $injector.get(dependency),
configurable: true,
writable: true
};
return acc;
}, {});
};
const invoke = function(target) {
Object.defineProperties(target, descriptors());
};
const revoke = function(target) {
Object.keys(descriptors()).forEach(x => delete target[x]);
};
const proxy: any = function(...args: any[]) {
if (this && this.constructor === proxy) {
invoke(ctor.prototype);
const instance = new (ctor as any)(...args);
revoke(ctor.prototype);
invoke(instance);
return instance;
} else if (ctor.apply) {
invoke(this);
return ctor.apply(this, args);
}
};
return proxy;
};
};
global["Inject"] = Inject;
global["Contextable"] = Contextable;
declare global {
var Inject: (ngDependencyName?: string) => PropertyDecorator;
var Contextable: (ngModuleName: string) => ClassDecorator;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment