Angular 1.x - What's going on with the order of $scope?

155 Views Asked by At

I have a controller where I need to load content using ajax. While it's loading, I'd like a spinner to appear in the interim. The code looks something like the below:

<i class="fa fa-2x fa-spin fa-spinner" ng-show="isLoadingContent"></i>

And the corresponding js:

$scope.isLoadingContent = true;
$q.all(promises).then(function (values) {
    $scope.isLoadingContent = false;
    // more code - display returned data

However, the UI the spinner does not appear where/when I expect it to appear when I step through the code.

$scope.isLoadingContent = true;
debugger; // the spinner does not appear on the UI
$q.all(promises).then(function (values) {
    debugger; // the spinner finally does appear in the UI at this point
    $scope.isLoadingContent = false;
    // more code - display returned data

I have tried stepping through the code but came up short as to what's going on -- and I am sure I am misunderstanding the sequence of events happening in the Event Loop and where the angular-cycle plays it's role in all of this.

Is someone able to provide an explanation as to why the spinner is set to appear within the promise's method rather than where I set $scope.isLoadingContent? Is it not actually getting set but rather getting queue'd up in the event-loop's message-queue?

------------ EDIT ------------

I believe I came across an explanation as to what's going on. Thanks in large part to, @jcford and @istrupin.

So a little tidbit missing in the original post, the event firing the promise calls and the spinner update was actually based around a $scope.$on("some-name", function(){...}) event - effectively a click-event that is triggered outside of my current controller's scope. I believe this means the $digest cycle doesn't work as it typically does because of where the event-origination is fired off. So any update in the $on function doesn't call $apply/$digest like it normally does, meaning I have to specifically make that $digest call.

Oddly enough, I realize now that within the $q.all(), it must call $apply since, when debugging, I saw the DOM changes that I had expected. Fwiw.

tl;dr - call $digest.

3

There are 3 best solutions below

9
On

A combination of both answers will do the trick here. Use

$scope.$evalAsync()

This will combine scope apply with timeout in a nice way. The code within the $evalAsync will either be included in the current digest OR wait until the current digest is over and start a new digest with your changes.

i.e.

$q.all(promises).then(function (values) {
    $scope.$evalAsync($scope.isLoadingContent = false);
});
4
On

Try adding $scope.$apply() after assigning $scope.isLoadingContent = true to force the digest. There might be something in the rest of your code keeping it from applying immediately.

As pointed out in a number of comments, this is absolutely a hack and is not the best way to go about solving the issue. That said, if this does work, you at least know that your binding is set up correctly, which will allow you to debug further. Since you mentioned it did, the next step would then be to see what's screwing up the normal digest cycle -- for example triggering outside of angular, as suggested by user JC Ford.

0
On

I usually use isContentLoaded (as oposite to isLoading). I leave it undefined at first so ng-show="!isContentLoaded" is guaranteed to show up at first template iteration.

When all is loaded i set isContentLoaded to true.

To debug your template you need to use $timeout $timeout(function () { debugger; }) That will stop the code execution right after first digest cycle with all the $scope variable values reflected in the DOM.