0

I have this function which returns json with $scope.product_owners = data

$http({
    url: "php/functions.php",
    method: "GET",
    params: { 
        action: "get_product_owners"
    }
}).success(function(data) {
    $scope.product_owners = data;
});  

Currently, I'm calling this function in all my controllers because it is used in them, but I'm wondering if it was possible to call once. For example, with the $rootScope or something like that.

Steffi
  • 6,835
  • 25
  • 78
  • 123

4 Answers4

3

The "Angular way" for sharing data across controllers is to use a service:

app.factory('ProductOwners', function ($http) {
    var service = {
        data: []
    };

    $http({
        url: "php/functions.php",
        method: "GET",
        params: { 
            action: "get_product_owners"
        }
    }).success(function(data) {
        service.data = data;
    });

    return service;
});

Then inject the service in every controller:

app.controller('someCtrl', function (ProductOwners) {
    $scope.product_owners = ProductOwners.data;
});

A different implementation with "lazy" evaluation (i.e. it only makes the call if it is needed and then serves the same data):

app.factory('ProductOwners', function ($http, $q) {
    var data;

    function getDataIfNeeded() {
        if (data !== undefined) {
            return $q.when(data);
        }

        return $http({
            url: "php/functions.php",
            method: "GET",
            params: { 
                action: "get_product_owners"
            }
        }).then(function(response) {
            data = response.data;
            return data;
        });
    }

    return {
        getData: getDataIfNeeded
    };
});

app.controller('someCtrl', function (ProductOwners) {
    ProductOwners.getData().then(function (data) {
        $scope.product_owners = data;
    });
});

UPDATE

Yet another different implementation with "lazy" evaluation and supporting an argument passed to getData():

app.factory('GenericService', function ($http, $q) {
    var data = {};

    function getDataIfNeeded(action) {
        action = action || 'default';

        if (data[action] !== undefined) {
            return $q.when(data[action]);
        }

        return $http({
            url: "php/functions.php",
            method: "GET",
            params: { 
                action: action
            }
        }).then(function(response) {
            data[action] = response.data;
            return data[action];
        });
    }

    return {
        getData: getDataIfNeeded
    };
});

app.controller('someCtrl', function (GenericService) {
    GenericService.getData("get_product_owners").then(function (data) {
        $scope.product_owners = data;
    });
});
gkalpak
  • 47,844
  • 8
  • 105
  • 118
  • I'm little confused. You said `service` but you use `factory`. I think it's not the same ? And what do you think about the @z.a's first solution ? Yours seems really great but complicated ? – Steffi May 09 '14 at 12:43
  • Regarrding service vs factory: It is one of the most confusing things about Angular (especially for newcomers), but you can register an Angular "service" with many different functions (including provider(), factory(), service(), constant(), value() etc). Actually all of them internally use the `provider()` function and are only syntactic sugar for simpler cases (such as yours). So, yes, calling `app.factory(...)` will make you an Angular service. – gkalpak May 09 '14 at 12:48
  • 1
    Regarding @z.a. 's answers vs mine: The first answer of z.a. is identical to my first answer (so no comment there). My second answer is indeed more complicated but provides the benefit that the data is fetched from the server **only** if needed (e.g. if someone asks for them). If the dataset isn't of negligible size or you are targeting mobile devices (where the number of requests has a noticable impact) this answer is worth the extra complication. – gkalpak May 09 '14 at 12:55
  • Finally, I wouldn't recommend @z.a. 's second answer, because it might lead to obscure bugs where some child scope's property "shadows" the mainCtrl's `product_owners` (property). Not to mention how testability and maintainability are negatively impacted. It is generally considered good practice to share data using services (hints: injectable + singleton). – gkalpak May 09 '14 at 12:55
  • Okay, great, thanks for these explanations. I'll try your solution 'the lazy one'. It seems good. I let you know @ExpertSystem ! ;) – Steffi May 09 '14 at 12:59
  • Your solution works great. I have 3 others identical functions, it's just the parameter `action` that changes. Can I use some argument in `getDataIfNeeded(...)`, or on the `factory()` ? – Steffi May 09 '14 at 14:20
  • Using an argument on the factory makes no sense since services are singletons, but you could ofcourse use and argument with the `getData()` function. (But you will also need to keep a reference to three different datasets (one for each action).) – gkalpak May 09 '14 at 14:26
  • I added yet another possible implementation to support `action` parameters. – gkalpak May 09 '14 at 14:31
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/52403/discussion-between-steffi-and-expertsystem) – Steffi May 09 '14 at 14:31
  • 1
    @Steffi: I had a bug in my last update (forgot to initialize `data`). I corrected my answer (replacing `var data;` with `var data = {};`). It should work correclty now. – gkalpak May 09 '14 at 15:08
2

If the same code is used by several controllers in your application, you may wish to put it inside a service and then you can call it from the controllers:

myApp.service('products', function($http) {
    this.getProductOwners = function(targetScope) {
        $http({
            url: "php/functions.php",
            method: "GET",
            params: { 
                action: "get_product_owners"
            }
        }).success(function(data) {
            targetScope.product_owners = data;
        });  
    };
});

And then, in your controllers:

myApp.controller('MyCtrl', function($scope, products) {
    products.getProductOwners($scope);
});

Using services is the preferred way for code reuse between several controllers.

urish
  • 8,943
  • 8
  • 54
  • 75
2

1- you can make a factory and then call it when needed inside controllers

yourApp.factory('httpFactory', function($http) {
return {
$http({
    url: "php/functions.php",
    method: "GET",
    params: { 
        action: "get_product_owners"
    }
}).success(function(data) {
    this.product_owners = data;
}); 
}}

then basically you inject it in to wherever,

yourApp.controller('xCtrl', function (httpFactory) {
    $scope.product_owners = httpFactory.product_owners;
});

2- you can also have a main controller for the app like this

<body ng-controller="mainCtrl">

and then put your code in that

yourApp.controller('mainCtrl', function($scope, $http) {
$http({
    url: "php/functions.php",
    method: "GET",
    params: { 
        action: "get_product_owners"
    }
}).success(function(data) {
    $scope.product_owners = data;
}); }

now you can access this data from any schild scope

z.a.
  • 2,549
  • 3
  • 12
  • 16
0

You can definitely place it on the $rootScope to save multiple calls.

Question is - if you place the one call on one controller, is it possible to go to a different view/controller and thus skipping the call ?

If the answer is no then you have no problem.

If the answer is yes then you should have a controller wrapping all the other ones to make sure you have that data.

Another possibility is have a service to keep the product_owners data, and each controller can access that server to get the data, and if it's not available, get it by the ajax request.

Omri Aharon
  • 16,959
  • 5
  • 40
  • 58