I'm working with the Soundcloud JS SDK to bring my Soundcloud favorites into a simple Angular app.
I wasn't able to get the user favorites to import in correctly until I used $scope.$apply
.
function TopListCtrl($scope, $http, $modal) {
$scope.getData = function(sc_user) {
SC.get('/users/'+ sc_user +'/favorites', {limit: 200}, function(tracks){
$scope.$apply(function() {
if (Object.getOwnPropertyNames(tracks).length > 1) {
$scope.likes = tracks;
$scope.sortField = 'like.favoritings_count';
$scope.reverse = true;
$scope.sc_user = sc_user;
}
else {
alert("That user has 0 Soundcloud likes. So sad...")
}
}).error(function (data, status, headers, config) {
alert("Something went awry with that request. Double check that's a real Soundcloud username");
})
});
}
If you don't use $scope.apply, it doesn't work (and says SC.get not defined).
I'd like to understand a bit better why $scope.$apply
is necessary. I ask this because when I was just using the http api, I didn't need it.
function TopListCtrl($scope, $http, $modal) {
$scope.getData = function(sc_user) {
var url = 'http://api.soundcloud.com/users/'+ sc_user +'/favorites.json?client_id=0553ef1b721e4783feda4f4fe6611d04&limit=200&linked_partitioning=1&callback=JSON_CALLBACK';
$http.jsonp(url).success(function(data) {
if (Object.keys(data.collection).length > 0) {
$scope.likes = data;
$scope.sortField = 'like.favoritings_count';
$scope.reverse = true;
$scope.sc_user = sc_user;
}
else {
alert("That user has 0 Soundcloud likes. So sad...")
}
}).error(function (data, status, headers, config) {
alert("Something went awry with that request. Double check that's a real Soundcloud username");
});
}
Usually angular knows about the code that's executing because you're the one providing the function callbacks but it's angular that's actually calling them. After angular calls a function, it will call $apply sometime later to trigger a $digest cycle.
If you don't know what a $digest cycle is, the concept is simple. During the $digest phase, angular will do a dirty check on every scope variable that's been set up with a $watch handler and check if it's changed; if it has angular will call its the corresponding $watch handler to update the view.
Getting back to the original question - when angular knows about your code, it will trigger a $digest cycle for you - so there is no need to call $apply explicitly. If you handle a jquery event, that's a different story. Angular has no idea that a $digest might be needed - how can it? So $apply is needed to trigger the $digest manually.
I know that you've already received the correct response to your question. I thought I'd also mention that it's not terribly difficult to use $http to make requests to the Soundcloud API, so that you won't need to use $scope.$apply. Here are mine:
var request = function(method, path, params, callback) {
params.client_id = sc.soundcloud.client_id;
params.oauth_token = sc.soundcloud.access_token;
$http({
method: method,
url: sc.soundcloud.api.host + path,
params: params
})
.success(callback);
};
get: function(path, params, callback) {
request('GET', path, params, callback);
},
put: function(path, params, callback) {
request('PUT', path, params, callback);
},
post: function(path, params, callback) {
request('POST', path, params, callback);
},
delete: function(path, params, callback) {
request('DELETE', path, params, callback);
}
Pixelbits' answer and an article by Jim Hoskins on $scope.$apply helped me understand this a little better. Here's the crucial point per my original question:
So, when do you need to call $apply()? Very rarely, actually.
AngularJS actually calls almost all of your code within an $apply
call. Events like ng-click, controller initialization, $http callbacks
are all wrapped in $scope.$apply(). So you don’t need to call it
yourself, in fact you can’t. Calling $apply inside $apply will throw
an error.
You do need to use it if you are going to run code in a new turn. And
only if that turn isn’t being created from a method in the AngularJS
library. Inside that new turn, you should wrap your code in
$scope.$apply()
(emphasis mine)
I'm still hazy on turns but I get the crucial point is that the method (SC.get
in my case) isn't part of the AngularJS library so I, therefore, need to use $apply
.
(At least I think I get it)