201

Within a module, a controller can inherit properties from an outside controller:

var app = angular.module('angularjs-starter', []);

var ParentCtrl = function ($scope, $location) {
};

app.controller('ChildCtrl', function($scope, $injector) {
  $injector.invoke(ParentCtrl, this, {$scope: $scope});
});

Example via: Dead link: http://blog.omkarpatil.com/2013/02/controller-inheritance-in-angularjs.html

Can also a controller inside a module inherit from a sibling?

var app = angular.module('angularjs-starter', []);

app.controller('ParentCtrl ', function($scope) {
  //I'm the sibling, but want to act as parent
});

app.controller('ChildCtrl', function($scope, $injector) {
  $injector.invoke(ParentCtrl, this, {$scope: $scope}); //This does not work
});

The second code does not work since $injector.invoke requires a function as first parameter and does not find the reference to ParentCtrl.

Claies
  • 22,124
  • 4
  • 53
  • 77
Federico Elles
  • 4,659
  • 7
  • 27
  • 35
  • This should help: http://stackoverflow.com/questions/16828287/what-things-can-be-injected-into-others-in-angular-js – Bart Aug 27 '13 at 10:14
  • 2
    aside: this doesn't look like inheritance, but more like sharing methods or injecting. Perhaps just semantics. – alockwood05 Jan 23 '15 at 21:01
  • The link for the example isn't valid anymore. – AlexS Jun 17 '15 at 05:15
  • Google Cache Link: http://webcache.googleusercontent.com/search?q=cache:vnn3hY3toEIJ:blog.omkarpatil.com/2013/02/controller-inheritance-in-angularjs.html+&cd=1&hl=en&ct=clnk which points to this interesting Fiddle: http://jsfiddle.net/mhevery/u6s88/12/ – Federico Elles Jun 18 '15 at 18:22

9 Answers9

295

Yes, it can but you have to use the $controller service to instantiate the controller instead:-

var app = angular.module('angularjs-starter', []);

app.controller('ParentCtrl', function($scope) {
  // I'm the sibling, but want to act as parent
});

app.controller('ChildCtrl', function($scope, $controller) {
  $controller('ParentCtrl', {$scope: $scope}); //This works
});
xav
  • 5,452
  • 7
  • 48
  • 57
Samuel Katz
  • 24,066
  • 8
  • 71
  • 57
  • `ParentCtrl` should be a `controller` or is it possible to use a `service` ? – gontard Mar 06 '14 at 13:54
  • @gontard: In this case it must be a controller, as `$controller` can only use registered controllers. – ZeissS Mar 13 '14 at 10:00
  • 10
    It is a very good solution. Thank you. But how would I do it in case I am using Controller As syntax? – To Ka Oct 24 '14 at 14:24
  • @ToKa Same as other controllers? `ChildCtrl as toka` – Samuel Katz Oct 24 '14 at 19:26
  • @ToKa What are you referring to by "Controller As syntax"? Show me some code. – Samuel Katz Oct 25 '14 at 23:44
  • How is it done with controller as?.. the jsfiddle above does not include it? – Robbo_UK Feb 17 '15 at 16:06
  • 1
    The above fiddle was asked as a question. It's worth noting that controllerAs simply assigns the controller to the scope - So you would change `$scope` to `this` (in theory) – Dan Mar 02 '15 at 14:15
  • @DanPantry Thanks, replacing `$scope` to `this` allows usage of `controller as` syntax – opewix Mar 30 '15 at 18:29
  • 4
    This worked for me, however I'm trying to do this in a way that I have the parent controller and the child controller on the same page. This causes the $http operation in the parent controller to run twice. When the child controller injects the scope of the parent controller my $scope.AllMembers array get's populated twice as the parent controller causes it to run, then the child controller causes it to run again. Is there any way to prevent that? – Ryan Mann Jul 15 '15 at 20:21
  • For people looking for solution with `var vm = this` convention: http://stackoverflow.com/a/34113156/140070 – IProblemFactory Dec 06 '15 at 02:30
  • how to be with other services not $scope that injected in the parent and chilld controllers. For instance, how can we use $routeParams in the parent controller? Is the parent's and child's services independed? – trueboroda Apr 05 '17 at 06:56
  • $controller documentation: https://docs.angularjs.org/api/ngMock/service/$controller – Nathan May 02 '17 at 16:33
  • Don't forget to declare the `$controller` in the `ChildCtrl`. – Radu Linu Oct 31 '18 at 07:48
20

In case you are using vm controller syntax, here is my solution:

.controller("BaseGenericCtrl", function ($scope) {

    var vm = this;
    vm.reload = reload;
    vm.items = [];

    function reload() {
        // this function will come from child controller scope - RESTDataService.getItemsA
        this.getItems();
    }
})

.controller("ChildCtrl", function ($scope, $controller, RESTDataService) {
    var vm = this;
    vm.getItems = RESTDataService.getItemsA;
    angular.extend(vm, $controller('BaseGenericCtrl', {$scope: $scope}));
})

Unfortunately, you can't use $controller.call(vm, 'BaseGenericCtrl'...), to pass current context into closure (for reload()) function, hence only one solution is to use this inside inherited function in order to dynamically change context.

IProblemFactory
  • 9,551
  • 8
  • 50
  • 66
8

I think,you should use factory or service,to give accessible functions or data for both controllers.

here is similar question ---> AngularJS controller inheritance

Community
  • 1
  • 1
LauroSkr
  • 2,159
  • 2
  • 15
  • 15
  • Yes that is one way, thanks. I came across that post when I was searching for solution. I was thinking if there was some way to load controller function and extend "this" with it. – To Ka Oct 25 '14 at 20:31
  • I would like to have a universal `loading` variable so that when data is loading I always do the same thing, I don't think factories can do that. My parent controller can have a loading variable but the factory can't manipulate it... right?! – Z2VvZ3Vp Sep 15 '15 at 00:16
8

In response to the issue raised in this answer by gmontague, I have found a method to inherit a controller using $controller(), and still use the controller "as" syntax.

Firstly, use "as" syntax when you inherit calling $controller():

    app.controller('ParentCtrl', function(etc...) {
        this.foo = 'bar';
    });
    app.controller('ChildCtrl', function($scope, $controller, etc...) {
        var ctrl = $controller('ParentCtrl as parent', {etc: etc, ...});
        angular.extend(this, ctrl);

    });

Then, in HTML template, if the property is defined by parent, then use parent. to retrieve properties inherited from parent; if defined by child, then use child. to retrieve it.

    <div ng-controller="ChildCtrl as child">{{ parent.foo }}</div>
Community
  • 1
  • 1
gm2008
  • 4,245
  • 1
  • 36
  • 38
5

Well, I did this in another way. In my case I wanted a function that apply the same functions and properties in other controllers. I liked it, except by parameters. In this way, all yours ChildCtrls need to receive $location.

var app = angular.module('angularjs-starter', []);

function BaseCtrl ($scope, $location) {
    $scope.myProp = 'Foo';
    $scope.myMethod = function bar(){ /* do magic */ };
}

app.controller('ChildCtrl', function($scope, $location) {
    BaseCtrl.call(this, $scope, $location);

    // it works
    $scope.myMethod();
});
Fabio Montefuscolo
  • 2,258
  • 2
  • 21
  • 18
5

For those wondering, you can extend component controllers in the same fashion, using the method in the accepted answer.

Use the following approach:

Parent component (to extend from):

/**
 * Module definition and dependencies
 */
angular.module('App.Parent', [])

/**
 * Component
 */
.component('parent', {
  templateUrl: 'parent.html',
  controller: 'ParentCtrl',
})

/**
 * Controller
 */
.controller('ParentCtrl', function($parentDep) {

  //Get controller
  const $ctrl = this;

  /**
   * On init
   */
  this.$onInit = function() {

    //Do stuff
    this.something = true;
  };
});

Child component (the one extending):

/**
 * Module definition and dependencies
 */
angular.module('App.Child', [])

/**
 * Component
 */
.component('child', {
  templateUrl: 'child.html',
  controller: 'ChildCtrl',
})

/**
 * Controller
 */
.controller('ChildCtrl', function($controller) {

  //Get controllers
  const $ctrl = this;
  const $base = $controller('ParentCtrl', {});
  //NOTE: no need to pass $parentDep in here, it is resolved automatically
  //if it's a global service/dependency

  //Extend
  angular.extend($ctrl, $base);

  /**
   * On init
   */
  this.$onInit = function() {

    //Call parent init
    $base.$onInit.call(this);

    //Do other stuff
    this.somethingElse = true;
  };
});

The trick is to use named controllers, instead of defining them in the component definition.

Adam Reis
  • 4,165
  • 1
  • 44
  • 35
2

As mentioned in the accepted answer, you can "inherit" a parent controller's modifications to $scope and other services by calling: $controller('ParentCtrl', {$scope: $scope, etc: etc}); in your child controller.

However, this fails if you are accustomed to using the controller 'as' syntax, for example in

<div ng-controller="ChildCtrl as child">{{ child.foo }}</div>

If foo was set in the parent controller (via this.foo = ...), the child controller will not have access to it.

As mentioned in comments you can assign the result of $controller directly to the scope:

var app = angular.module('angularjs-starter', []);
app.controller('ParentCtrl ', function(etc...) {
    this.foo = 'bar';
});
app.controller('ChildCtrl', function($scope, $controller, etc...) {
    var inst = $controller('ParentCtrl', {etc: etc, ...});

    // Perform extensions to inst
    inst.baz = inst.foo + " extended";

    // Attach to the scope
    $scope.child = inst;
});

Note: You then must remove the 'as' part from ng-controller=, because you are specifying the instance name in the code, and no longer the template.

gmontague
  • 33
  • 1
  • 6
  • Using "controller as" syntax has no problem. See my answer: http://stackoverflow.com/a/36549465/2197555 – gm2008 Apr 22 '16 at 15:03
2

I was using the "Controller as" syntax with vm = this and wanted to inherit a controller. I had issues if my parent controller had a function that modified a variable.

Using IProblemFactory's and Salman Abbas's answers, I did the following to have access to parent variables:

(function () {
  'use strict';
  angular
      .module('MyApp',[])
      .controller('AbstractController', AbstractController)
      .controller('ChildController', ChildController);

  function AbstractController(child) {
    var vm = child;
    vm.foo = 0;
    
    vm.addToFoo = function() {
      vm.foo+=1;
    }
  };
  
  function ChildController($controller) {
    var vm = this;
    angular.extend(vm, $controller('AbstractController', {child: vm}));
  };
})();
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-controller="ChildController as childCtrl" layout="column" ng-cloak="" ng-app="MyApp">
  <button type="button" ng-click="childCtrl.addToFoo()">
    add
  </button>
  <span>
      -- {{childCtrl.foo}} --
  </span>
</div>
Michael
  • 8,362
  • 6
  • 61
  • 88
dufaux
  • 681
  • 2
  • 8
  • 18
0

You can use a simple JavaScript inheritence mechanism. Also don't forget pass a needly angular services to invoke of .call method.

//simple function (js class)
function baseCtrl($http, $scope, $location, $rootScope, $routeParams, $log, $timeout, $window, modalService) {//any serrvices and your 2

   this.id = $routeParams.id;
   $scope.id = this.id;

   this.someFunc = function(){
      $http.get("url?id="+this.id)
      .then(success function(response){
        ....
       } ) 

   }
...
}

angular
        .module('app')
        .controller('childCtrl', childCtrl);

//angular controller function
function childCtrl($http, $scope, $location, $rootScope, $routeParams, $log, $timeout, $window, modalService) {      
   var ctrl = this;
   baseCtrl.call(this, $http, $scope, $location, $rootScope,  $routeParams, $log, $timeout, $window, modalService);

   var idCopy = ctrl.id;
   if($scope.id == ctrl.id){//just for sample
      ctrl.someFunc();
   }
}

//also you can copy prototype of the base controller
childCtrl.prototype = Object.create(baseCtrl.prototype);
trueboroda
  • 2,650
  • 26
  • 24