2

The code block below waits for a timeout, then performs an HTTP request with a promise, then changes an Angular Material scope variable based on the response. It is called during with an ng-change on an input field. I also have an element with ng-hide="showStuff" in my HTML, and obviously, when $scope.showStuff becomes false, I want it to disappear right away. Unfortunately, it doesn't actually disappear until I select something else in the DOM.

I've used promises before to change things in the DOM, and those work fine. Why doesn't the DOM update on its own, and how do I work around it?

$scope.checkSomething = function() {
    // Use a timeout to prevent a checks from going off too rapidly
    if (queryTimeout) {
        clearTimeout(queryTimeout);
    }
    queryTimeout = setTimeout(function() {
        bluebird.bind({}).then(function() {
            return makeHttpRequest();
        }).then(function(res) {
            $scope.showStuff = res.data.length > 0;
        })
    }, 500);
}
georgeawg
  • 48,608
  • 13
  • 72
  • 95
Christopher Waugh
  • 441
  • 1
  • 4
  • 15

2 Answers2

2

AngularJs's change detection (called the "digest cycle") doesn't know to run unless code began executing in an angular context. So asynchronous things don't work so well with that. That's why angular provides services like $timeout, $interval, and $q to do async things in such a way that it handles the kicking off of the digest cycle for you. Use those, and this type of problem will be exceedingly rare.

If you want to use another type of promise than $q, you'll need to kick off the digest cycle manually. You can do this either with $scope.$apply() if you want it to happen immediately, or $scope.$applyAsync if you want it to happen very soon, but not synchronously (useful if you might have multiple things calling this and you want them to be batched together).

So if you don't want to use $timeout and $q, you'd have to do something like this:

setTimeout(function() {
    bluebird.bind({}).then(function() {
        return makeHttpRequest();
    }).then(function(res) {
        $scope.showStuff = res.data.length > 0;
        $scope.$apply();
    })
}, 500);
Nicholas Tower
  • 72,740
  • 7
  • 86
  • 98
  • 1
    I'd rather check what @Christopher Waugh use for making HTTP request than force $digest with `$apply()`. Promises themselves should not trigger dirty check regardless what library is used. – skyboyer Aug 02 '18 at 20:41
  • AngularJS has it's own timeout method `$timeout` so that you don't have to use `$scope.$apply()`. But, yes I would be more interested in the HTTP request – Pop-A-Stash Aug 02 '18 at 21:08
  • Use the `$timeout` Service instead of `setTimeout`, then `$apply` is not needed. Keep in mind that in most places (controllers, services) `$apply` has already been called for you. An explicit call to $apply is needed only when implementing custom event callbacks, or when working with third-party library callbacks. – georgeawg Aug 02 '18 at 22:34
1

Use the $timeout service, it returns a promise that is integrated with the AngularJS execution context and its digest cycle:

$scope.checkSomething = function() {
    // Use a timeout to prevent a checks from going off too rapidly
    if (queryTimeout) {
        $timeout.cancel(queryTimeout);
    }
    queryTimeout = $timeout(function() {
        return makeHttpRequest();
    }, 500);

    queryTimeout.then(function(res) {
        $scope.showStuff = res.data.length > 0;
    })
}

This will delay execution of makeHttpRequest by 500 milliseconds. The $timeout Service returns a promise that be used to resolve the data from the server.

AngularJS modifies the normal JavaScript flow by providing its own event processing loop. This splits the JavaScript into classical and AngularJS execution context. Only operations which are applied in the AngularJS execution context will benefit from AngularJS data-binding, exception handling, property watching, etc.

By using the AngularJS $timeout service, the wrapped setTimeout will be executed in the AngularJS execution context.

For more information, see

georgeawg
  • 48,608
  • 13
  • 72
  • 95
  • All these answers are good, but I'm marking this one as correct because it mentions canceling the timeout, which is the reason I wanted to use timeout instead of promises. – Christopher Waugh Aug 03 '18 at 14:15