3

Building a simple control panel in AngularJS + Rest API.

Built a simple factory that makes the API request (GET, POST) and returns necessary data to a success callback. The return data needs to be processed and alter the $scope since the API can return server-side form field errors.

I cannot build the processing/altering $scope within the factory since the factory doesn't (and shouldn't) have access to the scope. I would prefer not to processing/applying in the success callback since it would be repetitive (once per API request).

What is the best "Angular way" to solve this problem?

One possible solution is to have a function exist outside of the Angular application and then just pass it the $scope and necessary data.

This feel like a poor work around (see below).

myApp.controller("saveForm", ["$scope", "api", function($scope, api), function() {
    ...
    $scope.submit = function() {
        api("POST", url, $scope.data, function(data) {
            //onSuccess
            processData($scope, data);
        });
    }
    ...
}]);

myApp.factory('api', ['$http', function($http) {
    return function(method, url, input, success, error) {
        //Retrieve data from API. Note that factory DOES NOT have access to $scope.
        $http(...
    }
}]);

var processData = function(scope, data) {
    angular.forEach(data, function(value, key)) {
        scope....
    }
}
Sagar Naliyapara
  • 3,971
  • 5
  • 41
  • 61
Jason
  • 4,079
  • 4
  • 22
  • 32

4 Answers4

1

Not sure I got you, but you can extend controllers in a mixin way:

Base controller

(function() {
  'use strict';

  angular.module('Base', []);

  function BaseController($scope, <injectables>, that) {
    that.mixin1 = function() {
    };

    that.mixin2 = function(arg1, arg2) {
    };
  }

  angular.module('Base').controller('BaseController',
    ['$scope', '...', BaseController]);
})();

Inherited controller

(function() {
  'use strict';

  angular.module('Derived', ['Base']);

  function DerivedController($scope, $controller, ...) {
    $controller('BaseController', {
      '$scope' : $scope,
      ...
      'that' : this
    });

    // this.mixin1
    // this.mixin2
  }

  angular.module('Derived').controller('DerivedController',
    ['$scope', '$controller', '...', DerivedController]);
})();

Note that you use Angular's $controller service to mix functionality.

madhead
  • 31,729
  • 16
  • 153
  • 201
0

Why not just include a function in your controller that handles it? This is how I usually handle it:

myApp.controller("saveForm", ["$scope", "api", function($scope, api), function() {
    ...
    $scope.submit = function() {
        api("POST", url, $scope.data, function(data) {
            //onSuccess
            processData(data);
        });
    }
    function provessData(data){
        // do stuff to data
        $scope.foo = data;
    }
    ...
}]);
Kolby
  • 2,775
  • 3
  • 25
  • 44
  • This does work, however, I have multiple controllers that would need to use processData(). With this implementation, I would have to include the function within each controller; duplicate code. – Jason Sep 22 '14 at 21:08
  • Try this then: http://stackoverflow.com/questions/19614545/how-can-i-add-some-small-utility-functions-to-my-angularjs-application – Kolby Sep 22 '14 at 21:10
  • Okay, so in that case you would recommend the solution I gave, but felt was messy since it existed outside of the Angular app. – Jason Sep 22 '14 at 21:12
  • Also, I think controllers are suppose to be more generic, data specific, and less functionality specific. Instead of a saveForm controller you probably need a generic form controller than handles all the form data, and a form directive that handles the form functionality. Why is it that you have multiple controllers that handle the same data? I would definitely suggest against breaking out a function from angular to handle it. There are solutions for handling util functions within Angular. – Kolby Sep 22 '14 at 21:14
  • that would be my bad. I whipped up a quick controller example for sample code. The control panel has several different types of content (each with an unique set of form fields). Each content type has it's own controller, but leverages the same API factory. – Jason Sep 22 '14 at 21:16
  • 1
    madhead's solution is exactly what you're looking for then. Create a base/util controller and inject it where you need it. – Kolby Sep 22 '14 at 21:17
0

It seems like your controller is doing a bit too much work and knows too much about the actual request (url, method of "POST", etc.).

What about transforming the data in the factory to something the controller expects. The factory doesn't need to know anything about scope. It just transforms the data into a format the controller can use and then sends it back. This way the factory can be reused across controllers.

myApp.controller("saveForm", ["$scope", "api", function($scope, api), function() {
    ...
    $scope.submit = function() {

// Call API.update and handle the deferred that is returned, which will be the transformed data
        api.update($scope.data).then(function (transformedData) {
           scope....

        });
    }
    ...
}]);

myApp.factory('api', ['$http', '$q', function($http, $q) {
    return {
         update : function () {

             var deferred = $q.defer();

             $http({
               method: "GET",
               url: "your/url/path"

             }).success(function(data) {

                var transformedData;

                // Perform data transformation here instead of in controllers
                angular.forEach(data, function (value, key) {
                    transformedData.....
                });

                deferred.resolve(transformedData);
             });

             return deferred.promise;
         }
    }
}]);
sma
  • 9,449
  • 8
  • 51
  • 80
  • This does work, however, it doesn't solve the problem. I have multiple controllers that use the `api` factory and process the data identically and alter the $scope. Placing it in each success callback would result in a lot of duplicate code. – Jason Sep 22 '14 at 21:11
0

You can create a factory that returns your processData function:

myApp.factory('processData', [function () {
    return function(scope, data) {
        angular.forEach(data, function(value, key)) {
            scope....
        }
    };
}]);

And then have it injected by angular:

myApp.controller("saveForm", ["$scope", "api", "processData", function($scope, api, processData) {
    ...
    $scope.submit = function() {
        api("POST", url, $scope.data, function(data) {
            //onSuccess
            processData($scope, data);
        });
    }
    ...
}]);

The advantage of this approach over having a function declared outside of DI is that it still easy to mock the processData dependency in your unit tests if you need to.

RichardTowers
  • 4,682
  • 1
  • 26
  • 43