When is $scope.$apply necessary for dealing with o

2019-07-16 05:44发布

问题:

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");         
        });
  }

回答1:

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.



回答2:

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);
    }


回答3:

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)