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.
//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();
});
}
}]);
source to share
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.
source to share
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>
source to share
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()
}
source to share