Skip to content
This repository has been archived by the owner on Apr 12, 2024. It is now read-only.

Adding a safe $scope.$apply() method or a queue for $scope.$digest() #3782

Closed
aliakhtar opened this issue Aug 28, 2013 · 11 comments
Closed

Adding a safe $scope.$apply() method or a queue for $scope.$digest() #3782

aliakhtar opened this issue Aug 28, 2013 · 11 comments
Milestone

Comments

@aliakhtar
Copy link

Please make $scope.$digest() work in a queue fashion, i.e if there are multiple calls for it, just process them in a queue rather than throw an error that an existing digest is running.

For example, I have a jquery plugin set up which is external to angularjs. Each time the user clicks a button produced by the plugin, it causes a variable to change, and in the plugin's on('plugin-change') method, I do $scope.$apply() to push the variable change.

However, what if a user clicks the button too quickly? While so far the digest runs fine in such cases, I'm concerned that if my scope grows big enough and a user clicks fast enough, this will cause the 'digest already running' error.

@mernen
Copy link
Contributor

mernen commented Aug 28, 2013

$apply() is synchronous, and the browser blocks user input while JavaScript is running on the main thread. Should the user perform multiple input events quickly, the spec guarantees the browser will queue those events.

Note that this can still happen with events triggered by code, though. If that's a possibility in your case, you should guard your $apply() call.

@jankuca
Copy link
Contributor

jankuca commented Aug 28, 2013

Use $scope.$evalAsync(function () { ... }) as it either triggers a $digest or causes a $digest rerun if there already is one in progress.

@aliakhtar
Copy link
Author

$scope.$evalAsync doesn't update the values for me, I have to do $scope.$apply . My request stands, please change $scope.$apply to work in queues rather than throwing up the error.

@jankuca
Copy link
Contributor

jankuca commented Aug 29, 2013

Can you make a demo? If you call $evalAsync from within a digest run, you don't get an error and another digest run is started right after the previous one.

// let's say there is an $apply (or a $digest) in progress
$scope.$apply(function () {

  // you code somewhere here
  $scope.$evalAsync(function () {
    $scope.x = 4;
  });
  // end of your code

});

If you have a {{x}} binding or a custom watcher, it will receive the correct value.

@aliakhtar
Copy link
Author

I didn't realize that $evalAsync had to be done inside the $apply. Still, it seems a really a long winded way of doing things, and will add a lot of boilerplate code for large projects. A much simpler solution would be to make $scope.$apply safe.

@jankuca
Copy link
Contributor

jankuca commented Sep 3, 2013

It does not need to be called inside $apply. If there is no digest/apply happening at the moment of calling $evalAsync, a $digest is scheduled right after that (setTimeout(digest, 0)) so you do not need to care about that.

Just call $evalAsync wherever you like and angular takes care of the rest.

@jankuca jankuca closed this as completed Sep 3, 2013
@aliakhtar
Copy link
Author

I have just tested this with the following code:

$el.on('switch-change', function(ev, data){ var state = data.value; scope.model = scope.bsSwitch.getValue(state); scope.$evalAsync(); });

And in this case the $evalAsync() has no effect. The {{}} bindings continue to show the old value for scope.model after the plugin's value is changed. Whereas if I replace evalAsync with a $scope.apply(), then the bindings do get updated.

@jankuca
Copy link
Contributor

jankuca commented Sep 3, 2013

Oh, I see. I think you are making the mistake of calling $evalAsync without any arguments. The method takes one argument which is either an expression or a function.

$el.on('switch-change', function(ev, data) {
  var state = data.value;
  scope.$evalAsync(function() {
    scope.model = scope.bsSwitch.getValue(state);
  });
});

$digest is triggered whenever the internal asyncQueue has items in it.

@aliakhtar
Copy link
Author

This is what I'm now trying:
$el.on('switch-change', function(ev, data){ var state = data.value; scope.model = scope.bsSwitch.getValue(state); scope.$evalAsync(function() { scope.model = scope.bsSwitch.getValue(state); }); });

And it has no effect either, scope.model in bindings is not updated after I change its value. If there's a way to send you a URL privately, I can send you the url to a demo.

@jankuca jankuca reopened this Sep 3, 2013
@jankuca
Copy link
Contributor

jankuca commented Sep 3, 2013

I see, you are having this issue right? http://plnkr.co/edit/2kqGi5LQoS51j3UduGCO?p=preview

This has actually been fixed in the master already and it is scheduled for the next release (1.2-rc2). This is the commit: 6b91aa0#L0R686

Here is the same demo using the latest angular build: http://plnkr.co/edit/WGBioumFO3qpqK7eRXlZ?p=preview

@jankuca jankuca closed this as completed Sep 3, 2013
@alexandrehebert
Copy link

Lodash can do the job : https://lodash.com/docs#debounce.
Angular now contains some useful features too : https://docs.angularjs.org/api/ng/directive/ngModelOptions.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants