Skip to content

Instantly share code, notes, and snippets.

@wvanderdeijl
Created April 11, 2017 09:38
Show Gist options
  • Save wvanderdeijl/a41e772f39c8c5d4301c7ab6f89e9ba3 to your computer and use it in GitHub Desktop.
Save wvanderdeijl/a41e772f39c8c5d4301c7ab6f89e9ba3 to your computer and use it in GitHub Desktop.
Decorators to allow angular controller subclasses without knowing anything about dependencies of base class
/**
* decorator especially for BaseController to block second call to its constructor. The constructor is invoked twice due to the
* trickery in the Controller decorator on any subclass of BaseController. The writer of that subclass will invoke
* `super()` in its constructor but we have to ignore this call to BaseController as the Controller decorator has already invoked
* BaseController with the required dependencies.
*/
function ConstructOnce(): ClassDecorator {
return (c: ControllerConstructor) => {
const wrapped = function (this: BaseController, ...args: any[]) {
// only invoke constructor the first time, and ignore second (no-arg) call
if (this['$$constructed']) { return; }
this['$$constructed'] = true;
c.apply(this, args);
};
__extends(wrapped, c);
return wrapped;
};
}
type ControllerConstructor = new (...args: any[]) => BaseController;
/**
* decorator for a subclass of BaseController to ensure the dependencies of both the subclass constructor and the
* BaseController superclass are injected. This removes the need of the decorated subclass to know anything about the
* dependencies needed by the BaseController super class and even allows this super class to add additional dependencies
* without requiring any need to the subclass or the no-arg super call in its constructor.
* Example:
* ```
* @Controller()
* class MyController extends BaseController {
* static $inject=['$log'];
* constructor(private $log: angular.ILogService) {
* super(); // assume the super class doesn't need any dependencies. The decorator will handle this.
* }
* }
* ```
*/
export function Controller(): ClassDecorator {
return (c: ControllerConstructor) => {
const ownInject = (c as Object).hasOwnProperty('$inject'); // does decorated class have its own $inject? (not from prototype)
const numInjects = ownInject ? c.$inject!.length : 0; // number of entries in $inject of decorated class
const wrapped = function (this: BaseController, ...args: any[]) {
// fist invoke BaseController constructor with "extra" arguments
BaseController.apply(this, args.slice(numInjects));
// ...then invoke decorated class constructor with its own dependencies
c.apply(this, args.slice(0, numInjects));
};
__extends(wrapped, c);
// make $inject on wrapped class a combination of the $inject of the decorated class and the $inject of the base class
wrapped.$inject = ownInject ? c.$inject!.concat(BaseController.$inject) : BaseController.$inject;
return wrapped;
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment