When is it safe to use $scope.$apply()?

前端 未结 7 1755
执笔经年
执笔经年 2020-12-09 17:47

I guess the title is pretty much clear what I am asking. I have created this fiddle : http://jsfiddle.net/Sourabh_/HB7LU/13142/

In the fiddle I have tried to replica

相关标签:
7条回答
  • 2020-12-09 18:29

    The $apply, should be used when the code is not executed in a angular digest loop. In normal circumstances we will not need to use it, but we might have to use it if we have a code that is called from a jQuery event handler or from methods like setTimeout(). Even if you have a function that is called from another angular function like a watch or angular event handlers you need not use $apply() as those scripts are executed in the digest cycle.

    One safe way is to check the $scope.$$phase param before calling $scope.$apply() like

    if($scope.$$phase){
        $scope.$apply();
    }
    

    In your case but you can use $timeout as suggested in the another answer

    • What is $$phase in AngularJS?
    • Why is using if(!$scope.$$phase) $scope.$apply() an anti-pattern?
    0 讨论(0)
  • 2020-12-09 18:36

    A better way is to use $scope.$applyAsync(); instead of $scope.$apply();

    The reason to avoid use of $scope.$apply() is given here:

    Error: [$rootScope:inprog] digest in progress. Fix

    0 讨论(0)
  • 2020-12-09 18:43

    Edit It was not clear the OP was trying to mock a backend call. Even so, using the $timeout service is a great way to avoid the need of calling $scope.$apply manually and is a more generally applicable solution than using a Promise (in cases where you're i.e. not calling $http it doesn't always make sense to force your changes into the next cycle by wrapping them with a Promise).


    Update your code to use the $timeout service and it should work without having to call $apply.

    $timeout is a wrapper around the native setTimeout with an important difference: $timeout will delay the execution at least until the next $digest cycle runs.

    So passing in no delay will still delay the execution up until the next cycle. Passing in 2000 will delay the execution up to the next cycle after 2000ms.

    Hence, this is an easy trick to make sure your changes are picked up by Angular without ever having to call $apply manually (which is considered unsafe in any case)

    function MyCtrl($scope, $timeout) {
      $scope.items = [{name : "abc"},{name : "xyz"},{name : "cde"}];
    
      $scope.change = function(){
        test(function(testItem){
          $scope.items = testItem;
          //$scope.$apply();
        })
      }
      function test(callback){
        var testItem = [
                        {name : "mno"},
                        {name : "pqr"},
                        {name :   "ste"}
                       ];
        $timeout(function(){callback(testItem)},2000);
      }
    }
    
    0 讨论(0)
  • 2020-12-09 18:44

    You need to use $apply every time you use something that is not "angular way", like Anzeo told about $timeout.
    For example if you use jQuery's http instead of angular's $http, you will have to add $scope.$apply.

    0 讨论(0)
  • 2020-12-09 18:45

    As @gruberb pointed out in the comments, if you tried to mock a REST call, you better use a promise than $apply.

    For this you need to use $q service to create and return a promise. Then simply call it and work with you result by calling then() method on the returned promise.

    function MyCtrl($scope, $q) {
        $scope.items = [{name : "abc"},{name : "xyz"},{name : "cde"}];
    
        $scope.change = function(){
    
            test().then(function (items) {
                $scope.items = items;
                $scope.$apply();
            });
        };
    
        function test() {
            var defered = $q.defer();
    
            var testItem = [
                {name : "mno"},
                {name : "pqr"},
                {name :   "ste"}
            ];
    
            setTimeout(function() {
                defered.resolve(testItem);
            },2000);
    
            return defered.promise;
        }
    }
    
    0 讨论(0)
  • 2020-12-09 18:48

    All the above answers give some information but they did not answer few doubts I had or at the least I did not understand. So I'm giving my own.

    It is very clearly stated in angular docs when to use $apply

    the call backs of $http or $timeout or ng-click, ng-..... have $apply() wrapped in them. Therefore when people say you don't have to use $apply() when you do angular way of doing things, this is it. However, one of the answers mentions angular event handlers are also wrapped with $apply(). This is not true or by that the user means just ng-click kind of events (again ng-....). If the event is broadcast on rootScope (or any scope for that matter) outside of $http or $timeout or ng-click, for eg: from a custom service, then you need to use $apply() on your scope although $rootScope.$broadcast is also angular way of doing things. in most of the scenarios we will not need this because the state of the app changes when something happens. ie, clicks, selection change, etc... and these are in angular terms are already using $apply() when we use ng-click ng-change respectively. Handling server side events using signalr or socket.io and while writing custom directives where the necessity to change just the directive's scope are few examples where it is very essential to use $apply()

    0 讨论(0)
提交回复
热议问题