div with ng-show not updating after ajax call

EmmyS picture EmmyS · Mar 13, 2014 · Viewed 17.9k times · Source

Very new to angular; I'm sure I'm missing something really obvious.

I have a login form on my page. What I want to happen is to have the form hide after the user successfully logs in.

My entire page uses a single controller.

Here's the HTML; I've stripped out all but the login fields, although there is actually other content inside the same controller:

<div ng-app="Localizer" ng-controller="StoreListCtrl" ng-init="my_occasion = ''">
    <div ng-show="user_signed_in===false">

        <div class="row"><h1>Have an account?</h1>
            <p>Sign in to quickly access addresses saved to your account</p></div>
            <div class="row">
                <div class="data">
                    <input type="username" ng-model="username" name="username" data-placeholder="Username" />
                </div>
                <div class="data">
                    <input type="password" ng-model="password" name="password" data-placeholder="Password" />
                </div>
                <div class="data"> 
                    <div class="syo-acctSignBtn yButtonTemplate" ng-click="login_user()">Sign In<div class="btnArrow iconPosition"></div></div>
                </div>
        </div>
    </div>
</div>

When the user clicks the Sign In button, a function called login_user() runs. This works; if I manually refresh the page, I do see that I'm logged in and the div that needs to be hidden is hidden. But without refreshing the page, the div remains visible, even though the variable it relies on (user_signed_in) does get updated.

Here's the login_user function in my controller; I've stripped out a lot of stuff, including front-end field validation:

$scope.login_user = function() {
    $.post('/site/ajax/login/do_login', {'username': $scope.username, 'password': $scope.password}, function(data) {
        if (data.success) {
                    $window.user_state.status = $window.user_status_key.STATE_SIGNED_IN;
            $scope.user_signed_in = $window.user_state.status == $window.user_status_key.STATE_SIGNED_IN;
            console.log("user status: " + $window.user_state.status );
                    console.log("user status key: " + $window.user_status_key.STATE_SIGNED_IN);
                    console.log("scope.user_signed_in: " + $scope.user_signed_in);
        } else {
            Modal({
                title: "Could not Login",
                text: data['errMsg'],
                yellow_button: {btn_text: "OK"}
            });
        }
    });

};

This is the result of the console.log lines:

user status: signed in user status key: signed in scope.user_signed_in: true

Since user_signed_in does not equal false, I would expect all the form content shown inside the <div ng-show="user_signed_in===false"> div to hide. But it doesn't (again, unless I manually refresh the page.) What am I missing?

Answer

gkalpak picture gkalpak · Mar 13, 2014

The problem is that you update the variable (user_logged_in) outside of the Angular context, so Angular knows nothing about it (until you refresh the page for example).

You should wrap the body of your callback in $scope.$apply():

$apply() is used to execute an expression in angular from outside of the angular framework. (For example from browser DOM events, setTimeout, XHR or third party libraries). Because we are calling into the angular framework we need to perform proper scope life cycle of exception handling, executing watches.

(emphasis mine)


BTW, you wouldn't face the same problem if you performed your request using the $http service, because it is Angular-context-aware.