2
\$\begingroup\$

I've been reading about AngularJS services and factories, and how you should strive for a thin controller, but as the project I've been working on grows it seems more and more impractical with as interconnected as the app is. For a little background, I am using UI-Grid to display most data in the app (20+ grids) and MVC5. At the moment, I've placed all of my API calls to SQL within the main controller, along with all of my grid definitions:

app.controller('MainController', ['$scope', '$http', '$filter', 'logger', function ($scope, $http, $filter, logger) {

    $scope.project = {};

    /*
    * Grid Listing Definition
    */
    $scope.gridListing = { enableRowSelection: true, enableRowHeaderSelection: false, multiSelect: false, enableColumnMenus: false, enableSorting: false };
    $scope.gridListing.onRegisterApi = function (gridApi) {
        // Set api on scope
        $scope.gridListing.Api = gridApi;
        gridApi.selection.on.rowSelectionChanged($scope, function (row) {
            $scope.gridListing.selectedRow = false; // Used for visibility on the view
            if (row.isSelected) {
                $scope.gridListing.selectedRow = row.entity;
            };
        });
    };
    $scope.gridListing.columnDefs = [
        { field: 'ListingNumber', displayName: 'Number', type: 'number', width: '10%' }
        , { field: 'CreatedOn', displayName: 'CreatedOn', type: 'date', cellFilter: 'date:"yyyy-MM-dd"', width: '10%' }
        , { field: 'CreatedBy', displayName: 'CreatedBy', width: '10%' }
        , { field: 'Description', displayName: 'Description' }
        , { field: 'FunctionState', displayName: 'State', width: '10%' }
    ];

    $scope.load_gridListing = function () { // Update when new Listing is selected
        $scope.gridListing.data = []; // Clear grid for new data
        $http.get('/api/qryListingAPI/?project=' + $scope.project.ProjectID)
        .then(function (response) {
            $scope.gridListing.data = response.data;
        })
        .catch(function (response) {
            // Custom logger for error tracking
            logger.log(usrName, 'app', response.status + " - " + response.statusText, logger.Error);
        });
    };
}]);

As the user interacts with objects in the view, like selecting a new project etc. various grids need to be updated to reflect the users selection. I had tied these "load" functions on the view to update grids as needed. I had originally thought of creating services for each of the views that I use to populate these grids, but with so many, and needing to share between controllers, I elected not to to avoid massive controller definitions and added complexity making sure each controller was on the same page. I may be misunderstanding how to implement AngularJS the way many say AngularJS should be implemented and making it more complicated than it needs to be. Any suggestions for implementing an app with 20+ grids and 30+ APIs?

\$\endgroup\$
1
\$\begingroup\$

it seems more and more impractical with as interconnected as the app is

You mentioned 30+ APIs. Let's start with that. If all of a sudden, someone from the API team decides to change the semantics for several of the APIs, where would you be starting your end of the change?

Another scenario would be if your manager told you to scrap your code because you'll have to start a new project... using the same API. You'll of course throw away the template and controller because they're app-specific. But the way you wrote your code, you'll also be throwing away all your API. Nothing is spared.

The Angular Way™ of doing this is that anything data-related should live in factories/services while anything UI related lives in the controller, templates and directives. Reasons for doing so includes:

  • You write the data source just once.
  • You can easily share data sources across different controllers.
  • You can easily throw away the UI layers without involving the data layer.

I normally start writing Angular apps by defining my data sources. Once that's done, I build the UI layers and just pull in the dependencies I need. If I want user info in this controller as well as another controller, I don't have to write 2 AJAX calls. All I would need is to pull in the same factory and call the same function in both controllers.

In your app, start by writing every single one of those APIs in its own factory . This means the following code should be gone from the controller:

$http.get('/api/qryListingAPI/?project=' + $scope.project.ProjectID)

Now if you were thinking of writing every single API as an $http call, that's where you got it wrong. Angular has this thing called $resource. Hand it a REST-compliant endpoint and it provides you an object with REST methods. A factory could even be as simple as:

app.factory('QueryListing', function(){
  return $resource('/api/qryListingAPI/');
});

If you did it with factories, no matter how many grids you have, you only need to write the API just once in the service, and just pull in the service. Additionally, resources expose promises. You can easily pop in a then.

app.controller('MainController', function($scope, ..., QueryListing){
  QueryListing.get(...).$promise.then(function(){
    ...
  });
});
\$\endgroup\$
  • \$\begingroup\$ Is it normal to have 30+ or 50+ injections into your controllers? \$\endgroup\$ – Alex Apr 29 '16 at 15:13
  • \$\begingroup\$ @Alex Thats not normal. But a single controller having to depend on 50+ things isn't normal either. You should consider breaking that controller apart into smaller chunks. \$\endgroup\$ – Joseph Apr 29 '16 at 15:51

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.