17

I have simple app in AngularJS. I want to show messages dynamically when an AJAX request is made. Unfortunately it always in hidden state and I can't figure out why.

HTML:

<div ng-show="message">
    <h2>show</h2>
</div>

<div ng-hide="!message">
    <h2>Hide</h2>
</div>

AngularJS controller:

function merchantListController($scope, $http, $rootScope, $location, global) {
    $http({
        method: 'POST',
        url: global.base_url + '/merchant/list',
    }).success(function($data) {
        if ($data.status === 'success') {
            $scope.merchants = $data.data;

            $scope.$apply(function(){
                $scope.message = true;
            });
        }
    });
}

screenshot

Matthew
  • 7,831
  • 11
  • 56
  • 80
Namal
  • 1,931
  • 3
  • 15
  • 25

7 Answers7

23

The likely reason it is not working is because you are creating a new scope property within a child scope, instead of overwriting the message property in merchantListController's scope as you would have expected.

// The following assignment will create a 'message' variable 
// in the child scope which is a copy of the scope variable   
// in parent scope - effectively breaking two-way model binding.
$scope.message = true;

To resolve this, make sure that you bind by reference to a property on your model rather than to a property on scope.

HTML

<div ng-show="my.message">
   <h2>show</h2>
</div>

<div ng-hide="!my.message">
   <h2>Hide</h2>
</div>

Controller

function merchantListController($scope, $http, $rootScope, $location, global) {

   // Initialize my model - this is important!
   $scope.my = { message: false };

   $http({
        method: 'POST',
        url: global.base_url + '/merchant/list',
    }).success(function($data) {

        if ($data.status === 'success') {
            $scope.merchants = $data.data;

            // modify the 'message' property on the model
            $scope.my.message   = true;
        }

    });
});

Explanation

The reason this works is because the model my is being resolved using scope inheritance rules. That is, if my does not exist in current scope, then search for my in the parent scope, until it is either found, or the search stops at $rootScope. Once the model is found, the message property is overwritten.

pixelbits
  • 48,078
  • 15
  • 86
  • 124
8

The show/hide logic is wrong... change it like: ng-hide="message"

    <div ng-show="message">
       <h2>show</h2>
    </div>

   <div ng-hide="message">
       <h2>Hide</h2>
   </div>

ng-hide needs variable when to hide, ng-show needs it when to show , so the condition ng-show="message" & ng-hide="!message" are the same.

Try doing this:

    <div ng-show="message">
       <h2>show</h2>
    </div>

   <div ng-show="!message">
       <h2>Hide</h2>
   </div>

Just for testing... change your http class to this:

$http({
    method: 'POST',
    url: global.base_url + '/merchant/list',
}).success(function($data) {

            $scope.message   = true;
    }).error(function($data) {
            $scope.message   = false;
    });
Spade
  • 582
  • 3
  • 17
harishr
  • 16,726
  • 7
  • 70
  • 114
  • 1
    the mess was I hadn't put ng-controller in html. But I thought it'll work with route controllers. Any idea? – Namal Jul 26 '14 at 11:21
1
Because you did a mistake...

ng-show="message" and ng-hide="!message" both points to the same value..

Correct it as..
<div **ng-show="message"**>
    <h2>show</h2>
</div>

<div **ng-hide="message"**>
    <h2>Hide</h2>
</div>

//or you can do in your way as...

<div **ng-show="message"**>
    <h2>show</h2>
</div>

<div **ng-show="!message"**>
    <h2>Hide</h2>
</div
Vishal Solanki
  • 2,145
  • 2
  • 16
  • 35
1

So, after spending time on a 2 year old question, I'll put something in here.

$scope.apply() is unnecessary, and probably would trigger an error saying that apply is already in progress. If you're doing it right, you should very rarely, or never call this function. It's main use would be if you use third party libraries, that angular couldn't watch for changes, or see what they do, and you need to trigger the digest cycle manually.

I would advise to initalize variables, so your controller would look like this (I've also extracted the http request to a service, and used then to handle responses and errors, since .success() have been deprecated):

function merchantService($http) {
    function post() {
    return $http.get('localhost');
    //Just a dummy $http request, instead of yours.
    //return $http.post(global.base_url + '/merchant/list');
  }

    /*  this is rather implicit, but if you are doing a service like this, 
        you can keep the functions "private" and export an object, that  
        contains the functions you wish to make "public" */

    return {
      post: post
  };
}

function merchantListController($scope, merchantService) {
    /* Initialize the variable, so ng-show and ng-hide 
       have something to work with in the beginning
       rather then just being undefined. */

    $scope.message = false;

    // call the post from the function
    merchantService.post()
  .then(function (response) {

    /* The first then() will hold the response
       if the request was OK */

    console.log(response);
    $scope.merchants = response.data;

    /* Since this then() will be triggered on a successful request
       we are safe to say $scope.message should now be true. */

    $scope.message = true;
  })
  .then(function (error) {

    // The second one is for the failed request.

    console.error(error);
  });
}

var app = angular.module('myApp', []);
app.controller('merchantListController', merchantListController);
app.factory('merchantService', merchantService);

You can find a working fiddle here: https://jsfiddle.net/vnjbzf3o/4/

Alex Szabó
  • 3,219
  • 2
  • 18
  • 30
1

The $scope.$digest() function iterates through all the watches in the $scope object, and its child $scope objects (if it has any). When $digest() iterates over the watches, it calls the value function for each watch. If the value returned by the value function is different than the value it returned the last time it was called, the listener function for that watch is called.

The $digest() function is called whenever AngularJS thinks it is necessary. For instance, after a button click handler has been executed, or after an AJAX call returns (after the done() / fail() callback function has been executed).

You may encounter some corner cases where AngularJS does not call the $digest() function for you. You will usually detect that by noticing that the data bindings do not upate the displayed values. In that case, call $scope.$digest() and it should work. Or, you can perhaps use $scope.$apply()

You can do like this :

HTML

<div ng-hide="message"> <h2>show</h2> <div>

<div ng-hide="!message"> <h2>Hide</h2> </div>

JS

$scope.message = true;


function merchantListController($scope, $http, $rootScope, $location, global) {
$http({
    method: 'POST',
    url: global.base_url + '/merchant/list',
}).success(function($data) {
    if ($data.status === 'success') {
        $scope.merchants = $data.data;

        $scope.$apply(function(){
            $scope.message = false;
            $scope.$digest();
        });
    }
});
Amit Kumar
  • 57
  • 13
0

First of all, the call to $scope.$apply() is unnecessary.

If the show div is never shown, that means that $scope.message = true; is not executed. If it's not executed, that means that either your AJAX request is not successful, or that $data.status === 'success' is never true.

Debug your code, or add console.log() traces to inspect the values of your variables, and see what is executed and what is not. Standard debugging work.

Also, don't prefix your data variable with a $. The $ is normally reserved to angular-provided services.

JB Nizet
  • 633,450
  • 80
  • 1,108
  • 1,174
  • Post the code of your template. If the scope message attribute is indeed true as your screenshot suggests, the html code you posted is probably not in the scope of the controller. I.e. it's not inside the HTML element that is controlled by this controller. – JB Nizet Jul 26 '14 at 09:03
  • the mess was I hadn't put ng-controller in html. But I thought it'll work with route controllers. Any idea? – Namal Jul 26 '14 at 11:20
-2

You don't need all that. Just do $scope.$parent.$message = true. As the earlier comment says its because you are in the Child scope and the original binding is with the parent scope variables. However I believe its stupid language to not have a common memory for both parent and child variables. It worked for me and didn't require mentioning the ng-controller. Such a crazy language, I wasted a day for this. However thanks for all the valuable feedback.

Anu Siril
  • 37
  • 1