javascriptangularjsangularjs-controlleras

Extend Angular controllers using controllerAs syntax & prototypical inheritance


I'm trying to extend controllers using conrollerAs syntax. My parent and child controllers are not defined in the same scope, so I put the parent controller (BaseController) in a service:

angular.module('myApp').factory('BaseController', function() {
  var BaseController = function(fooService, barService) {
    // base controller constructor logic
  }

  BaseController.prototype = {
    // base controller methods
  }

  return BaseController;
});

Then use it like so:

var ChildController = function(BaseController, fooService, barService) {
  BaseController.apply(this, [fooService, barService]);
}

var BaseController = angular.injector(['myApp']).get('BaseController');

ChildController.prototype = Object.create(angular.extend(BaseController.prototype, {
  fooMethod: function() {
    // do stuff
  }
}));

angular.module('myApp').controller('ChildController', ChildController);

I use ChildController in a ui router state. The state template doesn't load, and I get an error in the console:

Resource for page controller is not defined <div class="ng-scope" ui-view="foo-view">

Any ideas?


Solution

  • angular.injector creates a new injector instance (which is application instance, if it sounds better) and shouldn't be used in production within Angular app. I.e.

    angular.injector(['myApp']).get('BaseController') !== angular.injector(['myApp']).get('BaseController');
    

    You need to put your hands on BaseController dependency when you're still able to register controllers, and the only place for doing this is config phase,

    angular.module('myApp').config(function($controllerProvider, BaseController) {
      ...
      $controllerProvider.register('ChildController', ...)
    });
    

    This requires BaseController to be constant, not factory, and possibly limits the things you would like to do with it. Sounds less funny, doesn't it?

    So a better thing to do here is just

    var ChildController = function(BaseController, fooService, barService) {
      angular.extend(this, BaseController.prototype, { ... });
      BaseController.apply(this, [fooService, barService]);
    }
    
    angular.module('myApp').controller('ChildController', ChildController);
    

    Angular DI isn't suited well for OOP JS for the reasons shown above and still needs to be supplemented with other modular solutions.