15

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 replicate an async scenario. This is just an example but in an AJAX call if I don't use $scope.$apply() the list does not get updated. I want to know if it is safe to use $scope.$apply() every time I make an AJAX call to update a list or is there some other mechanism I can make use of?

Code I have written to replicate the scenario(same as in fiddle):

HTML

<div ng-controller="MyCtrl">
  <li ng-repeat="item in items">
    {{item.name}}
  </li>
  <button ng-click="change()">Change</button>
</div>

JS

var myApp = angular.module('myApp',[]);

function MyCtrl($scope) {
  $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"}
                   ];
    setTimeout(function(){callback(testItem)},2000);
  }
}
thomaux
  • 19,133
  • 10
  • 76
  • 103
Zee
  • 8,420
  • 5
  • 36
  • 58
  • 2
    Are you trying to imitate a REST call? If so: A $http request returns a promise, which you can use inside the controller with .then() to change the scope. Don't set a new scope and $apply() inside the REST call. Because, what you are doing now is pointless. – ohboy21 Apr 23 '15 at 08:00
  • I know it doesn't help now, but Angular 2.0 is set to fix this terribleness. – CodingIntrigue Apr 23 '15 at 08:02
  • 1
    Every scope-change runs through the digest. A timeout in a API call smells for me. What if the server needs more time? What if its getting called 2 or 3 times? – ohboy21 Apr 23 '15 at 08:06
  • @gruberb. Then in what scenarios we use apply(), is there a situation when nothing but only apply() works? Or in other words whenshould we use apply()? – Zee Apr 23 '15 at 08:09
  • 1
    I think when you feel the need of apply(), something could be done better. As $rootScope, $apply() should be used in very rare cases. https://github.com/angular/angular.js/wiki/When-to-use-$scope.$apply%28%29 – ohboy21 Apr 23 '15 at 08:14
  • http://www.codingeek.com/angularjs/angular-js-apply-timeout-digest-evalasync/ http://jimhoskins.com/2012/12/17/angularjs-and-apply.html – zloctb Aug 30 '15 at 13:14

7 Answers7

7

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);
  }
}
thomaux
  • 19,133
  • 10
  • 76
  • 103
  • I think the person wants to immidate a REST call, where the suggestion is to just use the promise with .success(), .then() or .error() instead of a timeout. – ohboy21 Apr 23 '15 at 08:12
  • That's not obvious from the question. The OP asks when it's safe to use $apply, which it is never so I provide an alternative. Using $q would of course also solve this issue but using `$timeout` is a much easier way to force your changes into the next cycle. – thomaux Apr 23 '15 at 08:15
  • @Anzeo. Sorry if I was unclear, however the 'test' function was just to imitate a server where the callback is actually a response from the server. +1 for the alternative. – Zee Apr 23 '15 at 08:17
  • If you're not using $http to make the async call, this solution is still preferred over manually calling `$scope.$apply` – thomaux Apr 23 '15 at 08:20
6

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.

Ivan Toncev
  • 532
  • 5
  • 15
6

If you want to immidate an API-Rest-Call, use the returned promise in your Controller instead setting the scope inside the Rest-Call.

$http.get('uri')
  .success(function(data) {
    $scope.items = data
});

Avoid using $apply(). From the Angular GitHub Repo:

$scope.$apply() should occur as close to the async event binding as possible.

Do NOT randomly sprinkle it throughout your code. If you are doing if (!$scope.$$phase) $scope.$apply() it's because you are not high enough in the call stack.

To your question:

  • If you find yourself in a situation where you need $apply(), rethink your structure.
  • Just for safety reason: Never use $apply()
ohboy21
  • 4,259
  • 8
  • 38
  • 66
  • It is not true that it is not safer to use $apply(). We cannot $apply(). It is gonna happen in an app's life cycle no matter what. It is a very important one to use and understand as and when appropriate. What is true is it is better not to call $apply() whenever possible, ie $timeout instead of setTimeout,... – Srivathsa Harish Venkataramana Dec 29 '15 at 16:38
  • The only reason I can think of not using $apply() is when you just want to use $digest(). because digest just evaluates the current scope's variables but $apply() ends up calling $rootScope.$digest() recomputing all the bindings which is very costly. However, if the use case is so then I don't think there is anything wrong in using ti – Srivathsa Harish Venkataramana Dec 29 '15 at 17:03
4

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

Pritam Banerjee
  • 17,953
  • 10
  • 93
  • 108
Arun P Johny
  • 384,651
  • 66
  • 527
  • 531
1

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

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;
    }
}
Community
  • 1
  • 1
Blackus
  • 6,883
  • 5
  • 40
  • 51
0

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

DevLoverUmar
  • 11,809
  • 11
  • 68
  • 98