Sign up ×
Stack Overflow is a community of 4.7 million programmers, just like you, helping each other. Join them; it only takes a minute:

I'm new to angular and am struggling to get to understand how to use a Service to pass data from one controller to another.

So far with with Angular, I have been able to understand how to call controllers and pass data to them from within HTML. That's been especially fine in order to set up a Map Controller, whereby I look through a list of results that comes from PHP, and send data from those results into my controller, to create markers on a map.

What has me completely stumped, is having one controller that generates a nested object via a ElasticSearch call and then passing that data to my Map Controller.

Initially I tried looping through the results via ng-repeat, and while I was able to output the results, my MapController wasn't able to read those results in the HTML as they were within the confines of my IndexController (which was outputting the data). Well I assume that was the case.

This is what I tried, and while it outputted the data, I could never read it in MapController.

<div ng-controller="IndexController">
    <div ng-repeat="r in listingsArea.results">
        <div class="component home-listings"
            data-id="{{r.id}}"
            data-url="{{r.url}}"
            data-lat="{{r.lat}}"
            data-lng="{{r.lon}}"
            data-address="{{r.address}}"
            data-price="{{r.price}}"
        ></div>
    </div>
</div>

I've read that the best way to pass data from one controller to another is via a service. I've followed a lot of documentation here, but despite this I am obviously missing something as it is not working.

This is what I have thus far:

ResultsService.js

App.factory("resultsService", function() {

var loadListingsArea = {};

return {
    getLocations: function() {
        return loadListingsArea
    },

    setLocations: function(loc) {
        loadListingsArea = loc;

IndexController.js

App.controller('IndexController', function($scope, resultsService) {

$scope.listingsArea = [];

$scope.loadListingsArea = function() {
    $http.get($window.location + '/listingsarea')
        .success(function(data) {
            $scope.listingsArea = data;
        }
    );
}

$scope.$watch(function() {
    return $scope.loadListingsArea;
}, function() {
    resultsService.setLocations($scope.loadListingsArea);
});
});

MapController.js (just trying to dump the object at this stage, not including my very long code base for the google map)

App.controller('MapController', function($scope, resultsService) {

$scope.locations = resultsService.getLocations();

alert($scope.locations);

This is a sample of what my object looks like when dumped from indexcontroller.js

{"count":4,
"results":[
      {"id":"1153292",
       "url":"/this-is-my-slug/1153292",
       "lat":"-37.822034",
       "lon":"144.969553",
       "address":"1302/430 Place Road",
       "price":"$2,350,000",
       "hero":"some-image-here.jpg"}
]};
share|improve this question
    
use a service to share, or use $broadcast to send directly – Joaozito Polo 22 hours ago
    
Oh I see, I wasn't aware of broadcast. So you can start that directly in one controller to send to another? – Ashkas 21 hours ago

2 Answers 2

up vote 1 down vote accepted

The $http service returns a promise. Store that. No need to do the $q.defer stuff.

App.factory("resultsService", function() {

  var self = this;
  self.listingsPromise = undefined;

  return {
      loadLocations: function(url) {
           self.listingPromise = $http.get(url);
           return self.listingPromise;
      },
      getLocations: function() {
        return self.listingPromise;
      }
  );
}

The controller that initiates the retrieve can do this:

resultsService.loadLocations() // returns a promise
  .then(function(response){
    $scope.listingsArea = response.data;
  }) .catch ( function (error) {
    throw error;
  });

And the controller that just needs to get it can do this:

resultsService.getLocations()
  .then(function(response){
    $scope.listingsArea = response.data;
  }) .catch ( function(error) {
    throw error;
  });

Notice that both controllers need to check for errors.

Also notice that .then returns data differently than .success.

The AngularJS team has come to their senses and deprecated .success. We all should be using .then from now on.

For information on the deprecation of .success and more info see the latest AngularJS $http API Docs

share|improve this answer
    
Tested and works a charm. From the way it runs, from my reading of this elements of the script wait for different parts to run before moving onto the next part. – Ashkas 17 hours ago
    
getLocations will return an empty object if called before loadLocations, and calling .then on it will fail. So when the second controller is included but the first is not, this will fail. Mine returns a promise either way and the .then will not fail, but also will not be resolved if loadLocations is never called. – mgiesa 17 hours ago
    
@mgiesa Look at Is this a “Deferred Antipattern”? – georgeawg 11 hours ago
    
Not entirely applicable because of the extra requirement here of accessing the data from a second controller. To add safety you would have to check in getLocations that self.listingPromise is in fact a promise, and if it isn't you would have to make one to return. You could change the method to lazy load, so if the promise doesn't exist yet you call the load function and get the promise. Or call the load method from both controllers and if the promise already exists return it instead of making a new request. But as is, your code will cause unhandled errors. – mgiesa 10 hours ago
    
That will still return the empty object becuase empty objects are truthy. Try doing a typeof, like if (self.listingPromise && typeof self.listingPromise.then === 'function') return self.listingPromise. or initialize listingPromise with null instead of {} – mgiesa 9 hours ago

Move the $http.get into a function in your service. Edited to use $q.deferred instead of returning the $http's promise.

App.factory("resultsService", function() {

  var self = this;
  self.deferred = $q.defer();
  self.listingsArea = {};

  return {
      loadLocations: function() {
          $http.get($window.location + '/listingsarea')
              .then(function(data){
                 self.listingsArea = data;
                 self.deferred.resolve(self.listingsArea);
              },
              function(e){
                self.deferred.reject(e);
              });
          return self.deferred.promise;
      },
      getLocations: function() {
        return self.deferred.promise;
      }
  );
}

The controller that initiates the retrieve can do this:

resultsService.loadLocations() // returns a promise
  .then(function(data){
    $scope.listingsArea = data;
  });

And the controller that just needs to get it can do this:

resultsService.getLocations()
  .then(function(data){
    $scope.listingsArea = data;
  });

This may require some extra massaging but it should give you a direction to head in.

Also note that sharing the object this way means you can edit the same object from either controller. To avoid this, pass around an angular.copy of the data object instead.

share|improve this answer
    
Hmmm something is breaking along the way with this, as by using your suggestion, the mapcontroller never fully loads and thus doesn't execute. Issue seems to be in the indexcontroller, as commenting out the code there then ensures that it executes. – Ashkas 21 hours ago
    
Is there an error logged in the console? – mgiesa 21 hours ago
    
Found that one, had to define $http and $window in the function. Still not 100% sure whether the full object is being shared though. – Ashkas 21 hours ago
    
I do something similar to this in my app. I have error callbacks and I don't put my data on $scope because I use the "controller as" syntax, that's about the only difference I can think of. This should share the object the way you need it to. – mgiesa 20 hours ago
    
Fair enough. Upon further inspection, I've been able to confirm that the service is retrieving the object, that the object is being passed to the indexController, but it's not making it to the mapController. At that point, getLocations is always returning null. – Ashkas 20 hours ago

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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