Why is this simple AngularJS ng-show not working?
Asked Answered
A

7

47

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);
}
Architectonics answered 5/4, 2014 at 13:15 Comment(1)
This post was helpful but the title doesn't lend itself to being googlable.Butter
I
65

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);
}
Ipsambul answered 5/4, 2014 at 13:19 Comment(6)
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().Architectonics
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.Ipsambul
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.Register
@Architectonics .apply() isn't a function, it's $apply().Jovitta
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.Jovitta
@Register Did you get the answer of your question here ? I also have the same query.Dorothi
B
14

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

Bulb answered 8/7, 2014 at 14:56 Comment(3)
I don't like this "trick", but worked for me (my $scope var is updated into a library callback). Thanks!Ejaculatory
@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.Ejaculatory
@dbautistav, you're right, is annoying and adds complexity. Thanks!Laminate
H
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

Harri answered 5/4, 2014 at 13:18 Comment(2)
if u already using $timeout then there is no need to use $scope.apply(); because angular make sure value get updated using apply.Lingo
$apply(), not .apply()Jovitta
P
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!

Potato answered 5/4, 2014 at 13:32 Comment(0)
B
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
            });
         }
    } 
Bidding answered 20/8, 2014 at 6:38 Comment(0)
D
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;
}
Dreadfully answered 8/8, 2016 at 0:48 Comment(0)
J
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.

Jar answered 2/5, 2018 at 21:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.