0

I've been trying to wrap my head around how to use angular $scope correctly. My app updates a view based on url links in json data it received from the server when loading the main page.

The controller fetches the next data file when clicking a button but I am unable to inject this data into my scope variable and update the view. The network transmits the data successfully so my service is working.

I don't understand how to make $scope resolve the $resource promise and update the view when i retrieve data, after the app has initialised. Greatly appreciate any help on this matter.

My ListController :

//View updates correctly when promise resolves
$scope.data = DataElements.get({}); 

$scope.nextPage = function (){ //Called when clicking 'next' button
var url = $scope.data.pager.nextPage; 

var pageNumber = url.split("page=").pop(); //Ugly but it is temporary, I promise..

//View doesnt update
DataElements.get({'page': pageNumber}, function(data){ 
    $scope.data = data; 
});

//Error: Digest already in progress
DataElements.get({'page': pageNumber }, function(data){ 
    $scope.$apply(function(){
      $scope.data = data; 
    });
});

//View doesnt update
$scope.data = DataElements.get({'page': pageNumber)};  

};

Service :

services.factory('DataElements', ['$rootScope','$resource', function($rootScope, $resource){
    return $resource($rootScope.baseUrl+'api/:endPointAdr.jsonp', 
        {'endPointAdr': 'dataElements', 'page': '@page'}, 
        { get : {'method' : 'JSONP', 'params' : {'callback' : 'JSON_CALLBACK'}}     
    });
}]);

View :

<section>
  <div>
    <h1>Search</h1>
    <input type="text" placeholder="Type something" autofocus>
    <input type="button" name="filter" value="Filter">
    <input type="button" name="clear" value="Clear">
    <div class="btn-toolbar" role="toolbar" aria-label="...">
      <a class="btn btn-default" href="#" aria-label="Previous Page">
        <span class="glyphicon glyphicon-chevron-left"></span>
      </a>
      <a class="btn btn-default" href="#" ng-click="nextPage()" aria-label="Next Page">
        <span class="glyphicon glyphicon-chevron-right"></span>
      </a>
    </div>
  </div>
  <div ng-controller="ListController">
    <ul class="" ng-show="data">
      <li class="" ng-repeat="element in data.dataElements ">
        <a href="#">
          <div class="info">
            <h2>{{element.name}}</h2>
          </div>
        </a>
      </li>
    </ul>
  </div>
</section>

Server response:

DataElements.get({}) == >
angular.callbacks._1({"pager": {"page":1,"pageCount":15,"total":706,"nextPage":"http// ...});(*)

DataElements.get({'page': 2})  == > 
angular.callbacks._2({"pager": {"page":2,"pageCount":15,"total":706,"nextPage":"http:// ...});(*)

(*) $resource strips the jsonp and only returns json.

Update :

If I only assign $scope.data in the nextPage function scope it works perfectly. I know the ng-clickdirective wraps this function in $scope.apply(). It then makes sense that explicitly calling apply causes an error.

Solution :

<div ng-controller="ListController"> created a duplicate controller with different $scope. Removing it fixed the problem. See my answer for details.

Natalie
  • 331
  • 4
  • 14

3 Answers3

1

If DataElements.get returns a promise object you need to call the then method:

DataElements.get({'page': pageNumber}).then(function(data) {
   $scope.data = data;
})

or if it's using $resource usually returns an object with a $promise property on it:

DataElements.get({'page': pageNumber}).$promise.then(function(data) {
   $scope.data = data;
})
Wawy
  • 6,259
  • 2
  • 23
  • 23
  • The promise property is present but calling .then or $promise.then only updates the data property on $scope, the view update still doesn't trigger. – Natalie Nov 16 '14 at 12:21
  • I did not state it clearly enough in my post, sorry. The $scope.data generates a list in the view. This list should update when the $scope.data changes. – Natalie Nov 16 '14 at 12:27
  • It's a [`$resource`](https://docs.angularjs.org/api/ngResource/service/$resource) object, and there is no need to call it with `$promise.then`, passing in the success callback as the OP did is ok. – marapet Nov 16 '14 at 12:28
1

I finally solved the problem.

It boiled down to this <div ng-controller="ListController">directive. It created a second ListController and attached it to the $rootScope. There was nothing wrong with the binding, but nextPage() was called from a different scope.

During the init phase of the dynamic ListController, both controllers would execute DataElements.get.., this is why it appeared to work, but calling the ng-click : nextPage() did not run in the context of the controller assigned to the list.htmlview.

Natalie
  • 331
  • 4
  • 14
  • 2
    That makes sense, however it would be nice to see the secondary usage of ListController in the HTML example provided. – Sirar Salih Nov 16 '14 at 19:16
0

As you mentioned, your data on $scope is getting updated but the change is not propagated to the view. Something is breaking the two-way binding.

See what kind of error you get in the console (F12 in Chrome).

Remove this code block:

//Error: Digest already in progress
DataElements.get({'page': pageNumber }, function(data){ 
    $scope.$apply(function(){
      $scope.data = data; 
    });
});

//View doesnt update
$scope.data = DataElements.get({'page': pageNumber)};  

Avoid using $apply explicitly, it's rarely a good idea.

Edit:

Although the OP solved his problem by removing a binding to ListController to avoid $scope confusion, the HTML code looks a bit strange as it is now. The optimal solution is to create a directive that binds to ListController, and then reuse this directive in the view. That way the OP will avoid such $scope issues in the future.

Sirar Salih
  • 2,514
  • 2
  • 19
  • 18
  • The $apply produce : Error: [$rootScope:inprog] $digest already in progress. I was trying to force angular to evalute its watchExpressions and detect that the data property has changed. I only used $apply as a measure during testing. Apply produce errors and the others fail silently. – Natalie Nov 16 '14 at 13:31
  • Yes, you get this error because Angular is already busy (in progress) digesting. At that time, you cannot force through your update to the view. See what's breaking your binding, any indications in the console? – Sirar Salih Nov 16 '14 at 13:40