45

I cannot figure out why my simple AngularJS app not working as intended. "Loading..." is supposed to be hidden, and "Done!" should be shown after 1 second.

html:

<div ng-app>
    <div ng-controller="TestCtrl">
        <div class="text-center" ng-show="loading">
            <h1>Loading...</h1>

    </div>
        <div class="text-center" ng-show="!loading">
            <h1>Done!</h1>

        </div>
    </div>
</div>

Javascript:

function TestCtrl($scope) {
    $scope.loading = true;
    setTimeout(function () {
        $scope.loading = false;
    }, 1000);
}
meager
  • 209,754
  • 38
  • 307
  • 315
darktideac
  • 453
  • 1
  • 4
  • 4

7 Answers7

63

You need to tell angular that you updated the var:

function TestCtrl($scope) {
    $scope.loading = true;
    setTimeout(function () {
        $scope.$apply(function(){
            $scope.loading = false;
        });
    }, 1000);
}

or just

function TestCtrl($scope, $timeout) {
    $scope.loading = true;
    $timeout(function () {
        $scope.loading = false;
    }, 1000);
}
Deepsy
  • 3,528
  • 5
  • 32
  • 66
  • 1
    Allright thanks, just getting started with AngularJS, didn't know about apply. Thanks for the help. I chose this answer - because in my real app (not the jsfiddle), I got an error with StarsSky's approach of using $scope.apply(). – darktideac Apr 05 '14 at 13:33
  • 2
    You're getting the error with StarSky's answer, because the $timeout starts a digest cycle and $apply is trying to start a second one, but you can't have more then 1 digest going on at time. – Deepsy Apr 05 '14 at 13:38
  • 32
    This is the right thing to do, but why? If I'm updating $scope with the new value, isn't that $scope property already on the $watch list, and should update the DOM automatically via the magic of two-way binding? Not understanding why $scope.$apply() is necessary. – dennis.sheppard Jan 22 '15 at 22:03
  • @darktideac .apply() isn't a function, it's $apply(). – Danny Bullis Feb 11 '15 at 19:24
  • Use $q.defer(); the object returned allows you to call a .resolve() or .reject() method once your async method is completed. Both resolve and reject methods of the $q.defer() object call $digest internally after the async method is resolved. The $digest tells angular to re-evaluate your $scope properties, which will do what you're looking for. It's a better practice. Avoid calling $apply() if you don't need to and if there are better, built-in angular mechanisms in place to take care of this. – Danny Bullis Feb 24 '16 at 01:18
  • @dennis.sheppard Did you get the answer of your question here ? I also have the same query. – Shivam Nov 13 '17 at 10:03
14

A nicer way of doing this is by calling $scope.$digest(); to update your UI

meager
  • 209,754
  • 38
  • 307
  • 315
TacoEater
  • 1,629
  • 15
  • 20
  • I don't like this "trick", but worked for me (my `$scope` var is updated into a library callback). Thanks! – David Mar 29 '16 at 02:45
  • @user2553863: I didn't like it because the framework _shouldn't_ need the developer notifies it _when_ to update the UI, but AngularJS does sometimes. Angular 2+ don't have this issue anymore. – David Apr 18 '18 at 16:25
  • @dbautistav, you're right, is annoying and adds complexity. Thanks! – user2553863 Apr 18 '18 at 22:36
6

You need to use $timeout and inject it in your controller:

function TestCtrl($scope, $timeout) {
    $scope.loading = true;
    $timeout(function () {
        $scope.loading = false;
    }, 1000);
}

Fiddle demo

Edit: removed $scope.apply(); as @Salman suggested

Community
  • 1
  • 1
StarsSky
  • 6,630
  • 6
  • 36
  • 62
6

You want to use apply() function to stop loading message.

Check this Demo jsFiddle**.

JavaScript:

function TestCtrl($scope) {
    $scope.loading = true;
    setTimeout(function () {
        $scope.$apply(function(){
            $scope.loading = false;
        });
    }, 1000);
}

Hope this would be help you!

meager
  • 209,754
  • 38
  • 307
  • 315
Jay Patel
  • 23,885
  • 12
  • 63
  • 74
3

when fire angular event to another object like setTimeout you should use

$scope.$apply(function(){
     $scope.loading = false;
});

for example

var loading={
     show:function(){
        $scope.loading=true
     },
     hide:function(){
        $scope.loading=false
     }
}  

may not working best way

   var loading={
         show:function(){
            $scope.$apply(function(){
               $scope.loading=true
            });
         },
         hide:function(){
            $scope.$apply(function(){
               $scope.loading=false
            });
         }
    } 
Behnam Mohammadi
  • 5,246
  • 35
  • 32
1

I have found that one way to work around ng-show not evaluating in the way you want it to be is to use ng-class instead.

 <div class="mycontent" data-ng-class="{'loaded': !loading}"> 

This way when $scope.loading is not equal to true the css class 'loaded' will be added to the element. Then you just need a to use the css class to show/hide the content.

.mycontent {
    display: none;
}

.loaded {
    display: block;
}
0

I think the biggest problem here is that you are using a primitive as your model. The angular team recommends to use an object to tie your model to. For example:

scope.model = {};
scope.model.loading = false;

Then in your html:

<div class="text-center" ng-show="model.loading">

That way angular gets a reference to a field inside an object instead of a primitive being pointed to by a variable.

mariogarcia
  • 151
  • 2
  • 4