196

I have three controllers that are quite similar. I want to have a controller which these three extend and share its functions.

isherwood
  • 46,000
  • 15
  • 100
  • 132
vladexologija
  • 6,637
  • 4
  • 27
  • 28

12 Answers12

304

Perhaps you don't extend a controller but it is possible to extend a controller or make a single controller a mixin of multiple controllers.

module.controller('CtrlImplAdvanced', ['$scope', '$controller', function ($scope, $controller) {
    // Initialize the super class and extend it.
    angular.extend(this, $controller('CtrlImpl', {$scope: $scope}));
    … Additional extensions to create a mixin.
}]);

When the parent controller is created the logic contained within it is also executed. See $controller() for for more information about but only the $scope value needs to be passed. All other values will be injected normally.

@mwarren, your concern is taken care of auto-magically by Angular dependency injection. All you need is to inject $scope, although you could override the other injected values if desired. Take the following example:

(function(angular) {

 var module = angular.module('stackoverflow.example',[]);

 module.controller('simpleController', function($scope, $document) {
  this.getOrigin = function() {
   return $document[0].location.origin;
  };
 });

 module.controller('complexController', function($scope, $controller) {
  angular.extend(this, $controller('simpleController', {$scope: $scope}));
 });

})(angular);
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.15/angular.js"></script>

<div ng-app="stackoverflow.example">
    <div ng-controller="complexController as C">
        <span><b>Origin from Controller:</b> {{C.getOrigin()}}</span>
    </div>
</div>

Although $document is not passed into 'simpleController' when it is created by 'complexController' $document is injected for us.

Enzey
  • 5,214
  • 1
  • 15
  • 19
  • 1
    By far the quickest, cleanest, and easiest solution for this! Thanks! – M K Jan 25 '14 at 21:58
  • perfect, awesome solution ! – Kamil Lach Jul 25 '14 at 12:19
  • 8
    I think you don´t need that `$.extend()`, you can simply call `$controller('CtrlImpl', {$scope: $scope});` – tomraithel Dec 16 '14 at 12:38
  • 5
    @tomraithel not using `angular.extend` (or `$.extend`) actually means extending the `$scope` only, but if your base controller also defines some properties (e.g. `this.myVar=5`), you only have access to `this.myVar` in the extending controller when using `angular.extend` – schellmax Dec 23 '14 at 09:46
  • Presumably if your extended controller has a lot of arguments, you will have to repeat all the arguments in the extending controller method signature, which in the end seemed a bit complicated to me. – mwarren Jan 19 '15 at 16:52
  • I have been wanting to reduce the complexity of my controllers by having them 'inherit' shared functionality, I had no idea this was so easy to do. AngularJS is great! Using this for Ionic apps is also great, as you can add shared behaviour like hiding/showing common things, $Ionicloading for example. – Michael Trouw May 06 '15 at 10:46
  • here is a problem with delayed initialization of simpleController. E.g. you have a promise, that sets some property later. The property will be set to simpleController, after extend (because initialization is run). So its probably easier to create simpleController instance, extend it with new properties, then return it. – ya_dimon May 28 '16 at 00:56
  • 1
    This is great! Just make sure to remember to extend all needed functions, i.e. I had something like: `handleSubmitClick` which would call `handleLogin` which in turn had a `loginSuccess` and `loginFail`. So, in my extended controller I then had to overload the `handleSubmitClick`, `handleLogin`, and `loginSucess` for the correct `loginSuccess` function to be used. – Justin Kruse Jun 22 '17 at 21:19
  • Not something you can do in comfortable way. – Petr Averyanov Jul 24 '17 at 18:47
52

For inheritance you can use standard JavaScript inheritance patterns. Here is a demo which uses $injector

function Parent($scope) {
  $scope.name = 'Human';
  $scope.clickParent = function() {
    $scope.name = 'Clicked from base controller';
  }    
}

function Child($scope, $injector) {
  $injector.invoke(Parent, this, {$scope: $scope});
  $scope.name = 'Human Child';
  $scope.clickChild = function(){
    $scope.clickParent();
  }       
}

Child.prototype = Object.create(Parent.prototype);

In case you use the controllerAs syntax (which I highly recommend), it is even easier to use the classical inheritance pattern:

function BaseCtrl() {
  this.name = 'foobar';
}
BaseCtrl.prototype.parentMethod = function () {
  //body
};

function ChildCtrl() {
  BaseCtrl.call(this);
  this.name = 'baz';
}
ChildCtrl.prototype = Object.create(BaseCtrl.prototype);
ChildCtrl.prototype.childMethod = function () {
  this.parentMethod();
  //body
};

app.controller('BaseCtrl', BaseCtrl);
app.controller('ChildCtrl', ChildCtrl);

Another way could be to create just "abstract" constructor function which will be your base controller:

function BaseController() {
  this.click = function () {
    //some actions here
  };
}

module.controller('ChildCtrl', ['$scope', function ($scope) {
  BaseController.call($scope);
  $scope.anotherClick = function () {
    //other actions
  };
}]);

Blog post on this topic

Minko Gechev
  • 23,174
  • 7
  • 57
  • 66
16

Well, I'm not exactly sure what you want to achieve, but usually Services are the way to go. You can also use the Scope inheritance characteristics of Angular to share code between controllers:

<body ng-controller="ParentCtrl">
 <div ng-controller="FirstChildCtrl"></div>
 <div ng-controller="SecondChildCtrl"></div>
</body>

function ParentCtrl($scope) {
 $scope.fx = function() {
   alert("Hello World");
 });
}

function FirstChildCtrl($scope) {
  // $scope.fx() is available here
}

function SecondChildCtrl($scope) {
  // $scope.fx() is available here
}
Andre Goncalves
  • 3,810
  • 2
  • 18
  • 15
  • Share same variables and functions across controllers that do similar things(one is for editing, another creating etc...). This is definitely one of the solutions... – vladexologija May 14 '13 at 10:22
  • 1
    $scope inheritance is by far the best way to do it, much better than the accepted answer. – snez Jul 11 '14 at 20:34
  • In the end this seemed the most angular way to go. I have three very brief parentControllers on three different pages which just set a $scope value which the childController can pick up. The childController, which I originally thought about extending, contains all the controller logic. – mwarren Jan 19 '15 at 16:48
  • wouldn't `$scope.$parent.fx( )` be a much cleaner way to do it, since that is where it is actually defined? – Aakash Sep 09 '16 at 06:52
15

You don't extend controllers. If they perform the same basic functions then those functions need to be moved to a service. That service can be injected into your controllers.

Bart
  • 16,076
  • 5
  • 54
  • 79
  • 4
    Thanks, but I have 4 functions that already use services (save, delete, etc..) and same exist in all three controllers. If extending is not an option, is there a possibility for a 'mixin' ? – vladexologija May 14 '13 at 10:00
  • Also there are variables that I also don't want to define three times. – vladexologija May 14 '13 at 10:02
  • Don't mix. You could create another service that handles those service methods (save, delete, ...). The more application logic you can move out of your controller the better. – Bart May 14 '13 at 10:07
  • 4
    @vladexologija I agree with Bart here. I think services are mixin's. You should attempt to move as much of the logic out of the controller as possible ( into services ). So if you have 3 controllers that need to do similiar tasks then a service seems to be the right approach to take. Extending controllers doesnt feel natural in Angular. – ganaraj May 14 '13 at 10:31
  • Ok, I can put functions in a service, no problem. What about variables, scope sharing etc.. – vladexologija May 14 '13 at 10:39
  • @vladexologija Can you post an example of a scenario? – ganaraj May 14 '13 at 10:57
  • @ganaraj nothing special to post. I just have variables that are used both in views and controllers and will be used in new service. I was wandering what's the best way for sharing, rootScope? – vladexologija May 14 '13 at 11:54
  • 6
    @vladexologija Here's an example of what I mean: http://jsfiddle.net/ERGU3/ It's very basic but you'll get the idea. – Bart May 14 '13 at 12:36
  • 1
    Thanks! What do you suggest if my functions use lot of $scope reference in them and this scope isn't available in the service? – vladexologija May 14 '13 at 13:41
  • You give me the impression that you are coupling the controller too tightly into your service. If you wan't a real answer you will have to provide some example code to illustrate your problem. – Bart May 14 '13 at 13:57
  • 1
    @vladexologija If you have a lot of variables to (re)set on `$scope` you can create a helper. It can do some dedicated tasks that don't fit well inside a service or are needed in more then one location. See http://jsfiddle.net/mFhge/1/ – Bart May 14 '13 at 14:30
  • 1
    Bart is absolutely correct. If you find yourself needing to extend a controller, then most likely you have a design issue. If you have 3 controllers all interacting with common elements then if it is via the UI, you need more of a child view implementation. If they are exchanging data then you should really be doing that from a service that can be shared. Controllers should not be tightly coupled to other controllers and they should not be tightly coupled to Services as well. You want the smallest testable components. – ewahner Sep 04 '15 at 15:38
  • 3
    The answer is pretty useless without any arguments and further explanation. I also think that you somewhat miss the OP's point. There already is a shared service. The only thing you do is to directly expose that service. I don't know if that's a good idea. Your approach also fails if access to the scope is needed. But following your reasoning I would explicitly expose the scope as a property of the scope to the view so that it can be passed as an argument. – a better oliver Nov 25 '15 at 18:26
  • 6
    A classic example would be when you have two form-driven reports on your site, each of which depend on a lot of the same data, and each of which use a lot of shared services. You could theoretically try and put all of your separate services into one big service with dozens of AJAX calls, and then have public methods like 'getEverythingINeedForReport1' and 'getEverythingINeedForReport2', and set it all to one mammoth scope object, but then you're really putting what is essentially controller logic into your service. Extending controllers absolutely has a use case in some circumstances. – tobylaroni Jan 15 '16 at 20:59
10

Yet another good solution taken from this article:

// base controller containing common functions for add/edit controllers
module.controller('Diary.BaseAddEditController', function ($scope, SomeService) {
    $scope.diaryEntry = {};

    $scope.saveDiaryEntry = function () {
        SomeService.SaveDiaryEntry($scope.diaryEntry);
    };

    // add any other shared functionality here.
}])

module.controller('Diary.AddDiaryController', function ($scope, $controller) {
    // instantiate base controller
    $controller('Diary.BaseAddEditController', { $scope: $scope });
}])

module.controller('Diary.EditDiaryController', function ($scope, $routeParams, DiaryService, $controller) {
    // instantiate base controller
    $controller('Diary.BaseAddEditController', { $scope: $scope });

    DiaryService.GetDiaryEntry($routeParams.id).success(function (data) {
        $scope.diaryEntry = data;
    });
}]);
Nikita Koksharov
  • 8,672
  • 52
  • 63
  • 1
    This worked extremely well for me. It has the advantage of easy refactoring from a situation where you started with one controller, created another very similar one, and then wanted to make the code DRYer. You don't need to change the code, just pull it out and be done. – Eli Albert Nov 29 '17 at 16:39
7

You can create a service and inherit its behaviour in any controller just by injecting it.

app.service("reusableCode", function() {

    var reusableCode = {};

    reusableCode.commonMethod = function() {
        alert('Hello, World!');
    };

    return reusableCode;
});

Then in your controller that you want to extend from the above reusableCode service:

app.controller('MainCtrl', function($scope, reusableCode) {

    angular.extend($scope, reusableCode);

    // now you can access all the properties of reusableCode in this $scope
    $scope.commonMethod()

});

DEMO PLUNKER: http://plnkr.co/edit/EQtj6I0X08xprE8D0n5b?p=preview

Raghavendra
  • 4,929
  • 4
  • 32
  • 49
5

You can try something like this (have not tested):

function baseController(callback){
    return function($scope){
        $scope.baseMethod = function(){
            console.log('base method');
        }
        callback.apply(this, arguments);
    }
}

app.controller('childController', baseController(function(){

}));
karaxuna
  • 25,822
  • 11
  • 76
  • 111
4

You can extend with a services, factories or providers. they are the same but with different degree of flexibility.

here an example using factory : http://jsfiddle.net/aaaflyvw/6KVtj/2/

angular.module('myApp',[])

.factory('myFactory', function() {
    var myFactory = {
        save: function () {
            // saving ...
        },
        store: function () {
            // storing ...
        }
    };
    return myFactory;
})

.controller('myController', function($scope, myFactory) {
    $scope.myFactory = myFactory;
    myFactory.save(); // here you can use the save function
});

And here you can use the store function also:

<div ng-controller="myController">
    <input ng-blur="myFactory.store()" />
</div>
aaafly
  • 2,408
  • 1
  • 12
  • 10
4

You can directly use $controller('ParentController', {$scope:$scope}) Example

module.controller('Parent', ['$scope', function ($scope) {
    //code
}])

module.controller('CtrlImplAdvanced', ['$scope', '$controller', function ($scope, $controller) {
    //extend parent controller
    $controller('CtrlImpl', {$scope: $scope});
}]);
georgeawg
  • 46,994
  • 13
  • 63
  • 85
Anand Gargate
  • 372
  • 3
  • 15
1

You can use Angular "as" syntax combined with plain JavaScript inheritance

See more details here http://blogs.microsoft.co.il/oric/2015/01/01/base-controller-angularjs/

Ori Calvo
  • 396
  • 4
  • 6
1

I wrote a function to do this:

function extendController(baseController, extension) {
    return [
        '$scope', '$injector',
        function($scope, $injector) {
            $injector.invoke(baseController, this, { $scope: $scope });
            $injector.invoke(extension, this, { $scope: $scope });
        }
    ]
}

You can use it like this:

function() {
    var BaseController = [
        '$scope', '$http', // etc.
        function($scope, $http, // etc.
            $scope.myFunction = function() {
                //
            }

            // etc.
        }
    ];

    app.controller('myController',
        extendController(BaseController,
            ['$scope', '$filter', // etc.
            function($scope, $filter /* etc. */)
                $scope.myOtherFunction = function() {
                    //
                }

                // etc.
            }]
        )
    );
}();

Pros:

  1. You don't have to register the base controller.
  2. None of the controllers need to know about the $controller or $injector services.
  3. It works well with angular's array injection syntax - which is essential if your javascript is going to be minified.
  4. You can easily add extra injectable services to the base controller, without also having to remember to add them to, and pass them through from, all of your child controllers.

Cons:

  1. The base controller has to be defined as a variable, which risks polluting the global scope. I've avoided this in my usage example by wrapping everything in an anonymous self-executing function, but this does mean that all of the child controllers have to be declared in the same file.
  2. This pattern works well for controllers which are instantiated directly from your html, but isn't so good for controllers that you create from your code via the $controller() service, because it's dependence on the injector prevents you from directly injecting extra, non-service parameters from your calling code.
Dan King
  • 2,915
  • 3
  • 19
  • 21
1

I consider extending controllers as bad-practice. Rather put your shared logic into a service. Extended objects in javascript tend to get rather complex. If you want to use inheritance, I would recommend typescript. Still, thin controllers are better way to go in my point of view.

Brecht Billiet
  • 219
  • 1
  • 5