1

This might be a question asked 10 thousand times but I still can't find a proper solution to this problem.

I just have the classical example of controller getting the data from a service, which in turn uses $http to fetch some data asynchronously from an API (or JSON file). Because the data in the controller are a reference to an object in the service, I was expecting the data to change when the service has done its job, and so I was expecting the view to update. As you can see in the snippet below the view retains the initial value.

Thus, my two questions:

  1. Is this a good pattern? Imagine that the controllers are more than one and that at certain point the data needs to be re-fetch (and so updated in any controller)
  2. Why the view is not updating ?

Keep in mind that I don't want to use $scope (if possible) nor $rootSscope, nor $watch.

var app = angular.module('myApp', [])
.service('Service', ['$http', service])
.controller('Controller', ['Service', controller]);


function controller(Service){
    var c = this;
    c.items = [
      {'title': 'Default title1'},
      {'title': 'Default title2'}
    ];
    c.items = Service.data;
}

function service($http, $scope){
    var data = [
      {'title': 'olddatatitle'}
    ];
    $http({
      method: 'GET',
      url: 'https://jsonplaceholder.typicode.com/posts?userId=1',
      cache: true
    }).then(function(response){
      data = response.data;
    });
    return {
      data: data
    }
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp" >
  <ul ng-controller="Controller as c">
    <li ng-repeat="item in c.items">
      {{item.title}}
    </li>
  </ul>
</div>
stilllife
  • 1,776
  • 1
  • 18
  • 38
  • the $http call executes asynchronously in your code. The return statement occurs immediately after you call $http and the `then` happens later. You need to either provide for a callback to update the data OR return a promise and let the caller specify their own callback. – Igor Oct 12 '16 at 20:58
  • Possible duplicate of [How do I return the response from an asynchronous call?](http://stackoverflow.com/questions/14220321/how-do-i-return-the-response-from-an-asynchronous-call) – Igor Oct 12 '16 at 21:00

3 Answers3

2

Your problem is an asynchronous one... You don't want to get your data from $http in your service and expect it to be ready when your controller is ready for it. Don't try to return data, instead return a promise of data:

function service($http, $scope){
    //var data = [
    //  {'title': 'olddatatitle'}
    //];

    return {
      getData: function() {
        return $http({    // calls to $http return a promise object
          method: 'GET',
          url: 'https://jsonplaceholder.typicode.com/posts?userId=1',
          cache: true
        });
    }
}

That way your controller will know exactly when the data is ready to be consumed:

function controller(Service){
    var c = this;
    //c.items = [
    //  {'title': 'Default title1'},
    //  {'title': 'Default title2'}
    //];
    Service.getData().then(function(response) {    // the promise object allows you to assign data only when it's ready
      c.items = response.data
    });
}
Pop-A-Stash
  • 6,572
  • 5
  • 28
  • 54
  • 1
    As a side note, your service is actually defined as a factory. Services don't use the `return` statement, but factories do, but this should give you the general idea – Pop-A-Stash Oct 12 '16 at 21:04
  • thanks for the clarification. I was wrongly assuming that 'data' gets updated anyways (after the async call) because its a reference to an object. I need to understand better how objects are passed around – stilllife Oct 12 '16 at 21:13
  • 1
    @stilllife - that could work if you passed in the object `data` and then updated that object with the new values. Alternatively you can return an object that you update later. However, your `then` code does not update the object but assigns your pointer a new object. At that point you have 2 objects in memory (short lived though as the pointer does not carry through). Every time you use the assignment operator you are reallocating that pointer to point to something else. – Igor Oct 12 '16 at 21:21
0
c.items = Service.data;

The above line never has the response of the api call, as by the time your api returns the response, JS has already moved on to the next statement... you need to provide a callback.

Thalaivar
  • 23,282
  • 5
  • 60
  • 71
0

You don't understand what promise is. You need actually return promise from Service, not data. As at the moment of service definition, there is no data. It only happens to be there after you complete your call for service. So part that works with promise need to be in calling code that uses service, not in service definition:

var app = angular.module('myApp', [])
.service('Service', ['$http', service])
.controller('Controller', ['Service', controller]);


function controller(Service){
    var c = this;
    c.items = [
      {'title': 'Default title1'},
      {'title': 'Default title2'}
    ];
    Service.get().then(function(payload) {
     console.log(payload);
      c.items = payload.data;
    });
}

function service($http, $scope){
    var data = [
      {'title': 'olddatatitle'}
    ];
    var get = function() {
     return $http({
      method: 'GET',
      url: 'https://jsonplaceholder.typicode.com/posts?userId=1',
      cache: true
    })};
    return {
      get: get
    }
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp" >
  <ul ng-controller="Controller as c">
    <li ng-repeat="item in c.items">
      {{item.title}}
    </li>
  </ul>
</div>
Sergey Moiseev
  • 2,953
  • 2
  • 24
  • 28