Can an AngularJS controller inherit from another controller in the same module?
Asked Answered
F

9

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.

Fisticuffs answered 27/8, 2013 at 9:3 Comment(4)
This should help: #16828787Freidafreight
aside: this doesn't look like inheritance, but more like sharing methods or injecting. Perhaps just semantics.Gynandromorph
The link for the example isn't valid anymore.Ruskin
Google Cache Link: webcache.googleusercontent.com/… which points to this interesting Fiddle: jsfiddle.net/mhevery/u6s88/12Fisticuffs
T
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
});
Thach answered 27/11, 2013 at 0:24 Comment(13)
ParentCtrl should be a controller or is it possible to use a service ?Khoury
@gontard: In this case it must be a controller, as $controller can only use registered controllers.Outgoing
It is a very good solution. Thank you. But how would I do it in case I am using Controller As syntax?Whitsun
@ToKa Same as other controllers? ChildCtrl as tokaThach
@ToKa What are you referring to by "Controller As syntax"? Show me some code.Thach
How is it done with controller as?.. the jsfiddle above does not include it?Fulgurating
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)Kitts
@DanPantry Thanks, replacing $scope to this allows usage of controller as syntaxEtruria
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?Leadin
For people looking for solution with var vm = this convention: https://mcmap.net/q/127297/-can-an-angularjs-controller-inherit-from-another-controller-in-the-same-moduleTabbatha
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?Countersink
$controller documentation: docs.angularjs.org/api/ngMock/service/$controllerLavona
Don't forget to declare the $controller in the ChildCtrl.Wall
T
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.

Tabbatha answered 6/12, 2015 at 2:5 Comment(2)
Couldn't you have just done this instead ? > $controller('BaseGenericControl', { vm: vm });Nunciature
vm is just a variable inside controller, I don't think so Angular could use it as expected.Tabbatha
K
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

Khartoum answered 27/8, 2013 at 9:13 Comment(2)
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.Whitsun
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?!Sesqui
I
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>
Indeterminable answered 11/4, 2016 at 13:1 Comment(0)
S
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();
});
Sidelight answered 25/9, 2015 at 13:22 Comment(0)
W
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.

Waller answered 14/1, 2017 at 22:41 Comment(0)
D
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.

Davie answered 19/6, 2015 at 20:35 Comment(1)
Using "controller as" syntax has no problem. See my answer: https://mcmap.net/q/127297/-can-an-angularjs-controller-inherit-from-another-controller-in-the-same-moduleIndeterminable
K
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>
Kimberly answered 18/8, 2016 at 13:15 Comment(0)
C
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);
Countersink answered 5/4, 2017 at 10:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.