Important info
This solution is implemented specifically for Angular-related code that's using actual Javascript prototypes to define controllers/services and not just anonymous functions as shown in everyday examples on the web - this means following John Papa's styleguide and extending it even further as he doesn't use real prototypes
All constructors must use explicit DI annotations using $inject
static property. This limitation can be somewhat worked around by
- using Angular injector's
annotate
(if annotations are provided inline - array) or
- changing
.inherits
signature to include all base type's constructor parameters in correct order as its own parameters i.e.
Child.inherits(Base, baseInjection1, baseInjection2, ...)
Setting proper prototypal inheritance in Angular
I've come up with a rather simple and most of all generic way to set type inheritance of my Angular controllers. Well this inheritance goes even beyond that and could be used with any Angular asset that uses type constructors (controllers, services, etc.)
Resulting solution changes original files' contents to this superbly simplistic form:
File1
angular.module("Test")
.value("BaseController", BaseController);
BaseController.$inject = [...];
function BaseController(...) {
...
}
BaseController.prototype.func = function () {
...
};
File2
angular.module("Test")
.controller("ChildController", ChildController);
ChildController.$inject = ["BaseController", ...];
function ChildController(BaseController, ...) {
// ALL MAGIC IS HERE!
ChildController.inherits(BaseController, arguments);
...
}
So all we have to do is make one call in our child type's constructor and provide base type (that's injected for us by Angular DI into constructor) and child type's constructor parameters (so inheritance can use them when running base type's constructor).
Implementing .inherit
functionality
To make things generic I've added this function to Function.prototype
object so it becomes available to all functions (or better yet constructors). This is how it's implemented:
Function.prototype.inherits = function inherits(BaseType, constructorInjections) {
/// <summary>Sets type's inheritance to base type.</summary>
/// <param name="BaseType" type="Function" optional="false">Base type to set for this child type.</param>
/// <param name="constructorInjections" type="Array" optional="true">Child type constructor injection instances.</param>
// return if both angular types don't use explicit DI
if (!this.$inject || !BaseType.$inject) return;
// DRY
if (this.prototype.__proto__ === BaseType.prototype || Object.getPrototypeOf(this.prototype) === BaseType.prototype) return;
// #region construct base object instance
// make a quick-search dictionary of child constructor injections
for (var i = 0, l = this.$inject.length, dic = {}; i < l; i++)
{
dic[this.$inject[i]] = i;
}
// create base type's constructor injections array
for (var i = 0, l = BaseType.$inject.length, baseParams = []; i < l; i++)
{
baseParams.push(constructorInjections[dic[BaseType.$inject[i]]]);
}
// get base type's constructed instance
var baseInstance = BaseType.apply(baseInstance = {}, baseParams) || baseInstance;
// #endregion
// #region set type inheritance chain
if (Object.setPrototypeOf)
{
Object.setPrototypeOf(this.prototype, BaseType.prototype);
}
else
{
// doesn't do the same thing, but it should work just as well
angular.extend(this.prototype, BaseType.prototype, { __proto__: BaseType.prototype });
}
// #endregion
// #region add base class generated instance to prototype
for (var i = 0, keys = Object.keys(baseInstance), l = keys.length; i < l; i++)
{
this.prototype[keys[i]] = baseInstance[keys[i]];
}
// #endregion
};