0

Background

I am making a census app about gnomes. This app shows a list of all gnomes and allows you to filter it.

Problem

The problem is that the filter is not working! My gnome-filter controller cannot communicate with the gnome-list controller. To solve this problem I tried creating a service by reading this answer in StackOverlflow:

However, after creating what I believe to be a fine replica of the answer, it is still not working.

Code

When the app first loads, gnome-list makes a request to the gnome census server to get all the gnomes and show them.

However, there are a lot of gnomes, so I offer you the option to filter them and only get those with read hair, by using the gnome-filter.

Unfortunately, even though I make a request to the server, and I get an answer, my service is not working...

/*global angular*/

(function() {
  var app = angular.module("census", []);

  app.controller("gnomeFilter", function(DataShareServ) {
    var self = this;
    self.listServ = DataShareServ;
    self.makeRequest = function() {
      self.listServ.request({
        hairColor: "red"
      });
    };
  });

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

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

    self.list.data = [];

    self.list.request = function(params) {
      var theUrl = 'https://gnome-shop-fl4m3ph03n1x.c9users.io/api/v1/gnomes';
      if (params.hairColor)
        theUrl = theUrl + "?hairColor=" + params.hairColor;

      $http({
        method: 'GET',
        url: theUrl,
        headers: {
          'Content-Type': 'application/json; charset=utf-8'
        }
      }).then(function successCallback(response) {
        self.list.data = response.data.entries;
        console.log("submitted");
      }, function errorCallback(response) {
        console.log('Error: ' + response);
      });
    };

    return self.list;
  });

})();
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.1/angular.min.js"></script>

<body>
  <!-- Search functionality -->
  <form ng-controller="gnomeFilter as filterCtrl" ng-submit="filterCtrl.makeRequest()">
    <button type="submit">Get Reddies !!</button>
  </form>

  <!-- Gnomes list -->
  <div ng-controller="gnomeList as listCtrl">
    <ul>
      <li ng-repeat="gnome in listCtrl.list">
        {{gnome.name}}
      </li>
    </ul>
  </div>
</body>

Questions

  1. What is wrong with my code? How can I fix it?
Community
  • 1
  • 1
Flame_Phoenix
  • 16,489
  • 37
  • 131
  • 266

2 Answers2

1

The obvious problem is that self.list is being reassigned.

After self.list = response.data.entries is done, self.list and DataShareServ.data refer to different objects.

It could be solved by keeping the reference to the same object with

self.list.length = 0;
Object.assign(self.list, response.data.entries);

Another problem is that controller does the job that it shouldn't, this results in design issues. $http requests shouldn't be performed in controller. DataShareServ service should contain methods that modify its own data.

Estus Flask
  • 206,104
  • 70
  • 425
  • 565
  • Hello, do you know of any readings regarding angular architecture that I could read? You make a good point I would like to learn about. – Flame_Phoenix Feb 05 '17 at 20:36
  • Not really about Angular, no. These are common MVC (in the context of Angular it is more like MVVM) concepts. The one I meant here is usually referred as 'fat model, slim controller' (a special case of 'separation of concerns' principle). Here's an example of what it may look like, https://scotch.io/tutorials/making-skinny-angularjs-controllers , it is a bit overcomplicated with router but gives some perspective. Anyway, if you have $http in controller, this means that something went wrong with design. – Estus Flask Feb 05 '17 at 21:10
  • By the way, the thing you're trying to achieve here is already done by `$resource`, it uses the same self-hydrating object technique under the hood that I've described in the answer. – Estus Flask Feb 05 '17 at 21:12
  • would you be able to provide a working minimal example for this code? I would deeply appreciate it. – Flame_Phoenix Feb 05 '17 at 21:26
  • Hello, I have updated my question taking in account your advice. Would you mind having a second look? – Flame_Phoenix Feb 05 '17 at 22:32
  • Just replace `self.list.data = response.data.entries` with `self.list.data.length = 0; Object.assign(self.list.data, response.data.entries)`, this is what the answer suggests (length = 0 empties the array, which is necessary for multiple requests). I don't think I can do anything minimal because it will differ from your code too much. If you will come up with a plunk and will have problems with it, feel free to update on that. – Estus Flask Feb 05 '17 at 22:52
1

Since you are assigning the DataShareServ to the scope of your gnomeList Controller, you could simply iterate over the Data which is stored in your service and which gets updated and reassigned on every request. This way you don't have to deal with syncing your objects on different Controllers.

This way, the html would look something like this:

<li ng-repeat="gnome in listCtrl.listServ.data">
   {{gnome.name}}
</li>

Edit

Also note, you have another Problem in your Code. In the gnomeList Controller, you are calling the request method of your Service which does an async request to some endpoint and assigns its result to a local variable, but you do not wait with assigning your controller variable to the result of the Service variable. So in simple Words:

self.list = self.listServ.data;

this will always be empty because the http request is async. You need to deal with Promises (or something like that). Take a look at the $q Service from angular: https://docs.angularjs.org/api/ng/service/$q

Note that this would not be important if you would go with my solution, just wanted to point that out.

Founded1898
  • 977
  • 5
  • 13