Reputation: 351
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-click
directive 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.
Upvotes: 0
Views: 3610
Reputation: 2544
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.
Upvotes: 0
Reputation: 351
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.html
view.
Upvotes: 1
Reputation: 6269
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;
})
Upvotes: 1