Ng-disabled does not evaluate after data change

I am writing an application where a user can update components by paying in game money. After clicking the "upgrade" button, an ajax request is sent to the server and updates the component. After that I issue another ajax request to fetch the new components page. But while the data is being refreshed, ng-disabled is not checked again, so the refresh button remains available if it shouldn't. Reloading the page or route fixes this. But reloading the page is not a good user experience.

I am doing this project to find out the Angularjs way, so if there is something wrong in my setup or how I am doing something, tell me!

I am trying to include all the relevant code, if something is missing you can view it under this link with user and password "test".

For headstart in, the machine-overview.html

button calls upgradeComponent

in the controller.js

controller MachineOverviewCtrl

, issuing an Ajax request and on successful update $scope.user

and $scope.machine

, the changed data is reflected in the view ( ng-repeat

), but the buttons are ng-disabled

not evaluated and retain the old state.

What am I doing wrong? How can I force the evaluation or make the update the way Angularjs likes it?

change

@ jack.the.ripper's answer is missing that there are multiple buttons, one for each ng-repeat, but those buttons just don't evaluate their ng-disable directive.

example image

//edit

index.html

<!DOCTYPE html>
<html lang="de" ng-app="hacksApp">
    <head>
        <!--ommited includes (angularjs, bootstrap etc)-->
    </head>
    <body ng-controller="hacksUserCtrl">
        <!--ommited navbar-->
        <div ng-view></div>
    </body>
</html>

      

machine-overview.html

<!--ommited divs-->
    <table class="table table-bordered">
        <!--ommited thead-->
        <tbody>
            <tr ng-repeat="component in machine | object2Array | orderBy:'ComponentID'"><!--converting objects to arrays, issue appears with and without filter-->
                <!--ommited other irrelevant td-->
                <td>
                    <button 
                    ng-disabled="
                    {{
                        (component.price_next <= 0) || (component.price_next > user.values.euro) || 
                        (user.values.energy_remaining < (component.energy_next-component.energy)) 
                    }}" 
                    ng-click="upgradeComponent(component)" class="btn btn-danger" role="button">
                        Upgrade {{component.text}} for <span class="glyphicon glyphicon-euro"></span> {{component.price_next}} 
                    </button>
                </td>
            </tr>
        </tbody>
    </table>
<!--ommited divs-->

      

javascript vars and their origins:

$scope.user.x //hacksUserCtrl
$scope.machine.x //MachineOverviewCtrl

      

app.js

var hacksApp = angular.module('hacksApp', [
    'ngRoute',
    'hacksControllers',
  'hacksFilters',
    ]);

hacksApp.config(['$routeProvider',
  function($routeProvider) {
    $routeProvider.
      when('/machine', {
        templateUrl: 'html/machine-overview.html',
        controller: 'MachineOverviewCtrl',
        activetab: 'machine'
      }).
      when('/jobs', {
        templateUrl: 'html/jobs-overview.html',
        controller: 'JobsOverviewCtrl',
        activetab: 'jobs'
      }).
      when('/phones/:phoneId', {
        templateUrl: 'html/phone-detail.html',
        controller: 'PhoneDetailCtrl',
        activetab: 'phone'
      }).
      otherwise({
        redirectTo: '/machine'
      });
  }]);

      

controller.js

var hacksControllers = angular.module('hacksControllers', ['hacksFilters']);

hacksControllers.controller('hacksUserCtrl', ['$scope', '$http', '$interval', '$route',
  function ($scope, $http, $interval, $route) {

    $scope.$route = $route;

    $scope.updateUser = function() {
        $http.get('api/user.php').success(function(data) {
          $scope.user = data;
          if(!$scope.user.loggedIn){
            window.location = "index.php";
          }
        });
    }
    $scope.updateUser();
    $interval($scope.updateUser, 60000);

  }]);

hacksControllers.controller('MachineOverviewCtrl',  ['$scope', '$http', '$interval',
  function ($scope, $http, $interval) {

  $scope.machine = [];

    $scope.updateMachine = function() {
        $http.get('api/machine.php').success(function(data) {
          $scope.machine = data;
        });
    }
    $scope.updateMachine();
    $interval($scope.updateMachine, 60000);

    $scope.upgradeComponent = function(component){

        var params = {name: component.name};
        $http({
            method: 'POST',
            url: 'api/machine-upgrade.php',
            data: $.param(params),
            headers: {'Content-Type': 'application/x-www-form-urlencoded'}
        }).success(function(data) {
          $scope.updateMachine();
          $scope.updateUser();
        });
    }

  }]);

      

+3


source to share


3 answers


When reading on $ scope. $ apply and $ scope. $ watch I found a solution for this: when I change the order of my queries everything works. Because when updating userdata (like money) there is no update called for the machine table. Changing the code to update $ scope.user for the first time, and then $ scope.machine after that, solves this problem. I am creating this community wiki.



+1


source


The main problem is that you are interpolating ng-disabled

instead of providing an angular expression. See Defining Arguments in the documentation . By interpolating (using ), the expression makes the expression evaluate only once per directive . {{}}

ng-disabled

Just change:

<button ng-disabled="
   {{
      (component.price_next <= 0) || (component.price_next > user.values.euro) || 
      (user.values.energy_remaining < (component.energy_next-component.energy)) 
   }}" 

   ng-click="upgradeComponent(component)" class="btn btn-danger" role="button">
       Upgrade {{component.text}} for <span class="glyphicon glyphicon-euro"></span>     
       {{component.price_next}} 
 </button>

      



to

<button ng-disabled="(component.price_next <= 0) || (component.price_next > user.values.euro) || 
      (user.values.energy_remaining < (component.energy_next-component.energy))" 

   ng-click="upgradeComponent(component)" class="btn btn-danger" role="button">
       Upgrade {{component.text}} for <span class="glyphicon glyphicon-euro"></span>     
       {{component.price_next}} 
 </button>

      

+16


source


your scenario: when the user clicks on the button, you disable it, and then when your operation is complete, you want to return to the pressed button.

what I have to do is create a directive that controls the state of the button when the button performs an operation, no matter what operation you perform ....!

baign advised that a possible answer to your problem might be to use promises and return a promise in your 'click' events, so you can set the button to turn off true when the user clicks it and turn it back on when your promise is resolved.

take a look at this directive:

angular.module('app').directive('clickState', [
    function() {
        return {
            priority:-1,
            restrict: 'A',
            scope: {
                clickState: '&'
            },
            link: function postLink(scope, element, attrs) {
                element.bind('click', function(){
                    element.prop('disabled', true);
                    scope.clickState().finally(function() {
                            element.prop('disabled', false);
                    });

                });
            }
        };
    }
]);

      

using:

<button click-state="updateMachine()" type="submit">submit</button>

      

controller:

var p = $q.defer()
$scope.updateMachine = function() {
        $http.get('api/machine.php').success(function(data) {
          $scope.machine = data;
          p.resolve(); //it could be p.resolve(data); to pass the data with the promise
        });
        return p.promise()
    }

      

+1


source







All Articles