1

I have two pages and two controllers that perform searches on the same dataset. One has a simple form to specify search criteria. The other allows the user to select data on a map (by selecting an area or clicking on features on the map).

The result of the search is then displayed on a data table below the search controls (on the same page).

So, the controllers have different search features, but also share a lot of common behaviour, i.e. displaying the grid, paging, reloading, batch edition of data from the grid etc.

As a result, there are several functions that are repeated, sometimes with slight variations, sometimes exactly identical, in both controllers.

The part of the page that displays the data is also a shared template, included with ng-include on both search pages.

Here is an example :

$scope.selectAllRows = function(){

    var selectedItems = $scope.searchState.selectedItems;

    angular.forEach($scope.searchState.searchResult.rows, function(row){
        // add only if not already selected
        if(selectedItems.indexOf(row) < 0){
            selectedItems.push(row);
        }
    });

};

This function selects all the rows in the table. It is bound to the ng-click of a button on the page.

There are many other functions like this, that basically read some state from the $scope, perform some logic, and place something new back to the $scope.

I would like to remove the duplication of these functions and regroup the common behaviour in a single unit of code.

Note : what I am trying to share here is behaviour, not data. These functions will perform the same logic, but on different scopes.

Since these are really presentation / UI stuff, and need to be put on the $scope, I don't think a service could be used because the $scope is a strictly controller thing and cannot be injected into services.

I am experimenting with defining a standalone function, in a different file, and calling this function from both controller :

myapp.defineCommonControllerBehaviour = function($scope, someOtherService){
    $scope.selectAlRows = function(){....}

    $scope.someOtherCommonTask = function(){
         someOtherService.doTheTask();
    }

    //etc...
};

//controller 1
myapp.defineCommonBehaviour($scope, someOtherService);

//controller 2
myapp.defineCommonBehaviour($scope, someOtherService);

It works but is not very elegant, since it defines a function in the global scope, outside of any Angular module.

Is there a angular native way of achieving this ? Or at least more in line with the Angular architecture ?

Pierre Henry
  • 16,658
  • 22
  • 85
  • 105
  • Sounds like you need a base controller (to establish common functionality from and extend as necessary). A search of SO shows several variations, and Google with lend itself to even more. (e.g. [this](http://jasonwatmore.com/post/2014/03/25/AngularJS-A-better-way-to-implement-a-base-controller.aspx) is just 1 (of many) examples, and passes `$scope` to the base, which I feel is what you're looking for). – Brad Christie Aug 12 '15 at 13:17
  • seems like you need move your grid functionality to directive – Grundy Aug 12 '15 at 13:19
  • 1
    Don't use a base controller, as you won't to avoid $scope inheritance, and functions on base controller are not passed to directives with isolated scope. Common functionality should be part of a service (logic) or a directive (view component). – Ori Drori Aug 12 '15 at 13:40

2 Answers2

2

There are some patterns which I found to be useful for my use cases.

Either neutral JS code can be used to define and inherit your controllers (the lack of dependency management and prototype inheritance don't make it the best option, but ES2015 classes are a great help in this case).

Or the one can make use of Angular dependency management and store common methods and properties in a service:

myApp.service('baseCtrl', function () {
  this.name = 'base';
  this.getName = function() {
    return this.name;
  };
});

myApp.controller('MainController', ['baseCtrl', function (baseCtrl) {
  angular.extend(this, baseCtrl);
  this.name = 'main';
}]);

There are third-party solutions such as Classy that provide opinionated inheritance syntax for controllers.

$injector or $controller can be also used to get access to parent controller and inherit it, but since controllerAs syntax was introduced, there's no need to mess around $scope anymore.

Estus Flask
  • 206,104
  • 70
  • 425
  • 565
0

Put common functions into commonCtrl's scope and access them in child controller. Angular child's scope inherit from parent's scope.

<div ng-controller='commonCtrl'>
    <div ng-controller='featureOneCtrl'></div>
</div>
<div ng-controller='commonCtrl'>
    <div ng-controller='featureTwoCtrl'></div>
</div>
gzc
  • 8,180
  • 8
  • 42
  • 62