3

Background

I am making Service which has a list to be shared between two controllers. To start I followed this tutorial on Services:

And I managed to successfully create and execute the basic tutorial on Plunker:

Problem

The problem here, is that when I click the form button, I need to make an HTTP GET request to my server, and update the service list when I get the response.

To achieve this, I first tried using the following Plunker modification:

The jest of the code can be seen in the service:

  // Create the factory that share the Fact
  app.factory('ListService', function($http) {
    var list = {};
    list.data = [];

    list.request = function(theHairColor) {
      var theUrl = "https://gnome-shop-fl4m3ph03n1x.c9users.io/api/v1/gnomes?hairColor=" + theHairColor;
      console.log(theUrl);
      $http({
        method: 'GET',
        url: theUrl,
        headers: {
          'Content-Type': 'application/json; charset=utf-8'
        }
      }).then(function successCallback(response) {
        list.data = response.data.entries; //does not work
        console.log(response.data.entries);
      }, function errorCallback(response) {
        console.log('Error: ' + response);
      });
    };

    return list;
  });

If you tried it out, you will see it simply doesn't work, and I don't really understand why. In a previous question I made, someone explained to me that it was related to references, and that I should replace list.data = response.data.entries; //does not work for the following:

//this works but is rather flimsy ....
list.data.length = 0;
Object.assign(list.data, response.data.entries);

Which in deed does work, but I find it rather counter intuitive:

Another suggestion was also given, in that I should change my gnomeList controller to :

app.controller("gnomeList", function(ListService) {
    var self = this;
    self.listService = ListService;
  });

and then iterate over the service's list directly:

<div ng-controller="gnomeList as listCtrl">
      <p ng-repeat="gnome in listCtrl.listService.data">{{ gnome.id }}: {{ gnome.name }}</p>
</div>

Which also works, but attaches the controller directly to the service:

Questions:

  1. Are there any other ways to make my first code sample (that didn't work) work?
  2. Which of these solutions would be preferable and why? (which one is more Angulary?)
Flame_Phoenix
  • 16,489
  • 37
  • 131
  • 266
  • Possible duplicate of [Share data between controllers using Service AngularJs 1.6.1](http://stackoverflow.com/questions/42055954/share-data-between-controllers-using-service-angularjs-1-6-1) – Estus Flask Feb 06 '17 at 10:31
  • @estus read both questions and you will understand that although related, they are not duplicated. – Flame_Phoenix Feb 06 '17 at 10:40
  • It looks like the same question but updated and bumped. There are two options, and they have been already voiced. You should either return a promise from service. Or you should return a self-filling object, with `Object.assign`. It is not flimsy. This is how built-in `$resource` works (which you could use in this case instead of reinventing the wheel). – Estus Flask Feb 06 '17 at 10:52
  • This question focuses on one thing: which of the two is the more Angulary?? The other one was just a fix for a problem that existed. This one focuses "which kind of fix is preferable". They are two different problems. – Flame_Phoenix Feb 06 '17 at 14:13
  • Their angularity is equal. It was a suitable fix. As it was said, this approach is used by $resource. But in this case a promise should be also returned from `request`. You never know when it is needed (at least for testing). – Estus Flask Feb 06 '17 at 14:17

2 Answers2

2

Problem is you initially copy the data in your gnomeList and it is passed by value.

app.controller("gnomeList", function(ListService) {
  var self = this;
  self.list = ListService.data;
});

When your controller gets initialized here, it puts a copy of ListService.data into self.list. However, when updating the values in the services, this controller does not get initialized again and therefore the value is not updated.

Objects in javascript are passed by reference. Just like you said, you could directly put the service on scope to use its data or you simply set the properties on an object before you set them on your scope. (Plunkr)

Javascript

app.controller("gnomeList", function(ListService) {
  var self = this;
  self.list = ListService.value; // value is an object
});

// Create the factory that share the Fact
app.factory('ListService', function($http) {
  var list = {};
  list.value = {};

  list.request = function(theHairColor) {
    var theUrl = "https://gnome-shop-fl4m3ph03n1x.c9users.io/api/v1/gnomes?hairColor=" + theHairColor;
    console.log(theUrl);
    $http({
      method: 'GET',
      url: theUrl,
      headers: {
        'Content-Type': 'application/json; charset=utf-8'
      }
    }).then(function successCallback(response) {
      list.value.data = response.data.entries; // extend value object
    }, function errorCallback(response) {
      console.log('Error: ' + response);
    });
  };

  return list;
});

HTML

<div ng-controller="gnomeList as listCtrl">
  <p ng-repeat="gnome in listCtrl.list.data">{{ gnome.id }}: {{ gnome.name }}</p>
</div>

Moreover it is better to use built in angular.extend for extending objects.

oshell
  • 8,923
  • 1
  • 29
  • 47
0

So this doesn't work?

list.request = function(theHairColor) {
  var theUrl = "https://gnome-shop-fl4m3ph03n1x.c9users.io/api/v1/gnomes?hairColor=" + theHairColor;
  console.log(theUrl);
  $http({
    method: 'GET',
    url: theUrl,
    headers: {
      'Content-Type': 'application/json; charset=utf-8'
    }
  }).then(function success(data) {
    list.data = data;
    console.log(data);
    return data;
  });
};

That would be a structure we've used in the past (we use Restangular now), but this link is a good page to see about $http that would help you.

rrd
  • 5,789
  • 3
  • 28
  • 36