I am writing an app where the user can upgrade components paying with ingame money. After pressing the "upgrade"-button an ajax request is send to the server and upgrades the component. After that I am issuing another ajax request to retrieve the new components page. But while the data gets updated, ng-disabled is not checked again, so the upgrade-button remains clickable when it should not. Reloading page or route fixes this. But reloading the page is no good user experience.
I am doing this project to learn the Angularjs way, so if there is anything wrong in my setup or how I do something, please tell me!
I try to include all relevant code, if something is missing, you can view it under this link with user and password "test".
For a headstart, in machine-overview.html
the button calls upgradeComponent
in controller.js
controller MachineOverviewCtrl
issuing an Ajax request and on success updating $scope.user
and $scope.machine
, changed data is reflected in the view (ng-repeat
) but the buttons ng-disabled
is not evaluated and keeps the old state.
What am I doing wrong? How can I force an evaluation or do the update in a way Angularjs likes it?
edit:
@jack.the.ripper's answer is missing the point, that there are multiple buttons, one for every ng-repeat, but those buttons only 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 origin:
$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();
});
}
}]);
I am writing an app where the user can upgrade components paying with ingame money. After pressing the "upgrade"-button an ajax request is send to the server and upgrades the component. After that I am issuing another ajax request to retrieve the new components page. But while the data gets updated, ng-disabled is not checked again, so the upgrade-button remains clickable when it should not. Reloading page or route fixes this. But reloading the page is no good user experience.
I am doing this project to learn the Angularjs way, so if there is anything wrong in my setup or how I do something, please tell me!
I try to include all relevant code, if something is missing, you can view it under this link with user and password "test".
For a headstart, in machine-overview.html
the button calls upgradeComponent
in controller.js
controller MachineOverviewCtrl
issuing an Ajax request and on success updating $scope.user
and $scope.machine
, changed data is reflected in the view (ng-repeat
) but the buttons ng-disabled
is not evaluated and keeps the old state.
What am I doing wrong? How can I force an evaluation or do the update in a way Angularjs likes it?
edit:
@jack.the.ripper's answer is missing the point, that there are multiple buttons, one for every ng-repeat, but those buttons only 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 origin:
$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();
});
}
}]);
Share
Improve this question
edited Sep 11, 2014 at 23:30
reggaemuffin
asked Sep 11, 2014 at 22:09
reggaemuffinreggaemuffin
1,1982 gold badges12 silver badges26 bronze badges
3 Answers
Reset to default 16Your main problem is you are interpolating ng-disabled
instead of providing it an angular expression. See the arguments definition in the documentation. Interpolating (using {{}}
) the expression makes the expression evaluated only once within the ng-disabled
directive.
Simply 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>
While reading up on $scope.$apply and $scope.$watch I found the solution to this: When I change the order of my requests, everything works. Because on updating the userdata (like money) there is no update triggered for the machine table. Changing the code to first update $scope.user and after that $scope.machine solves this problem. I am making this community wiki.
your scenario: when a user click on a button you disable it, then when your operation is complete you want to return to the button to enabled.
what I should do is to create a directive that manage the state of the button while the button is performing the operation, it doesn't matter what operation you are doing....!
baign said that, a possible answer to your issue could be to use promises and return a promise in your 'click' events, in that way you can set the button to disabled true when the user click it and enable it again when your promise gets resolved.
take a look to 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);
});
});
}
};
}
]);
usage:
<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()
}