angular scoped variable in controller not updating when value changed in service - angularjs

I'm developing a mini-basket in angular for an ecommerce application but have a problem with a scoped variable not updating via a service.
When i click on an add to basket button in the product grid it fires the upDateMiniBasket function in the product grid controller which has the UpdateMiniBasket service injected into it.
The controller:
whiskyControllers.controller('whiskyListCtrlr', ['$scope', 'UpdateMiniBasket', '$http',
function($scope, UpdateMiniBasket, $http){
$http.get('json/whiskies.json').success(function(data){
$scope.whiskies = data;
})
$scope.updateMiniBasket = function(e){
var targetObj = e.target.getAttribute('data-item');
UpdateMiniBasket.getUpdate(targetObj);
}
}
])
Here is the service:
whiskyrumApp.factory('UpdateMiniBasket', [function(){
var miniBasketTotal = 0,
itemCount = 0;
var miniBasketItems = [{
imageUrl : '',
name : 'There are currently no items in your basket',
price: 0
}]
var getUpdate = function(obj){
miniBasketTotal = 0;
if(obj) {
if(miniBasketItems[0].price === 0) miniBasketItems.pop();
obj = jQuery.parseJSON(obj);
miniBasketItems.push(obj);
}
for(var i = 0, j = miniBasketItems.length; i < j; i++){
miniBasketTotal += parseFloat(miniBasketItems[i].price);
}
itemCount = miniBasketItems[0].price === 0 ? 0 : miniBasketItems.length;
return {
miniBasketItems : miniBasketItems,
miniBasketTotal : miniBasketTotal,
itemCount : itemCount
}
}
return {
getUpdate : getUpdate
}
}]);
The problem is that when I add a product, the function fires and calls the service ok, but a scoped variable that should update the amount of items in the basket is not updating. This variable lives in another controller for the minibasket that also has teh UpdateMiniBasket service injected.
whiskyControllers.controller('miniBasketCtrlr', ['$scope', 'UpdateMiniBasket',
function($scope, UpdateMiniBasket){
var mbItems = UpdateMiniBasket.getUpdate();
$scope.miniBasketItems = mbItems.miniBasketItems;
$scope.itemCount = mbItems.itemCount;
}])
And this is html:
<div class="mini-basket grey-box" ng-controller="miniBasketCtrlr">
<a href="#" data-toggle="dropdown" class="btn dropdown-toggle">
{{itemCount}} Items
</a>
<!-- the ng-repeat code for the mini basket which works fine -->
I just assumed that when the variables in the service are updated, that would feed through to the scoped var in the other controller as they are both linked to the same service. I thought maybe I need to add a $watch as I cant see why this {{itemCount}} is not updating.
Any help would be greatly appreciated.

If you change the return object of your UpdateMiniBasket factory to instead of returning the primitive value for the count of items but to a function like below:
return {
miniBasketItems : miniBasketItems,
miniBasketTotal : miniBasketTotal,
itemCount : function () {
return itemCount;
}
}
And then in your controller change the binding to $scope to this:
$scope.itemCount = function () {
return mbItems.itemCount();
};
You should then just have to change the binding in the html to {{ itemCount() }} and this value should then successfully update when the values in the factory update.
I have a simplified solution to this on jsbin here: http://jsbin.com/guxexowedi/2/edit?html,js,output

Related

Angular/Ionic: updating ng-change value in two controllers

I need a bit of a code review, i'm having trouble getting my ng-change function to trigger and update the value in both controllers, i've created a factory service and have injected it into both controllers but on the second AppCtrl console.log() value prints only once during initialization, and would like to have the ng-change value also update on the second controller and not only on the first.
This is what i have so far:
<ion-radio ng-repeat="rate in rates"
ng-value="rate.id"
ng-change="rateTypeChanged(rate)"
ng-checked="rate.selected"
ng-model="currentRate.selectedRate">
{{ rate.title }}
</ion-radio>
controller for sidebar:
.controller('SidebarCtrl', function($scope, typesOfRates) {
$scope.rates = typesOfRates.rateType;
$scope.currentRate = {
selectedRate: 'hourly'
};
$scope.rateTypeChanged = function(rate) {
console.log("Selected goalType, text:", rate.title, "value:", rate.id);
typesOfRates.setRate(rate.id);
}
In controller 2:
.controller('AppCtrl', function($scope, typesOfRates, $state, $rootScope) {
console.log( typesOfRates.getRate() );
//runs only once, but not again when ng-change event is triggered
my service:
.factory('typesOfRates', function typesOfRates($rootScope) {
var typesOfRates = {};
typesOfRates.myRates = [];
typesOfRates.rateType = [
{ title: "Hourly", id: "hourly", selected: true },
{ title: "Daily", id: "daily", selected: false },
{ title: "Monthly", id: "monthly", selected: false }
];
typesOfRates.currentRate = "hourly";
var setRate = function(currentRate) {
if (typesOfRates.myRates.length > 0) typesOfRates.myRates = [];
typesOfRates.myRates.push(currentRate);
}
var getRate = function() {
return typesOfRates.myRates;
}
return {
rateType: typesOfRates.rateType,
getRate: getRate,
setRate: setRate
}
});
The way you are doing to achieve the objective seems bit out of the box. The second controller will be initialized only once. If you want to access the undated value in the second controller you need to following one of the following approaches.
1) Watch for changes in typesOfRates.myRates in the second controller.
$watch is used to track changes for a model variable in the scope. The
$watch requires $scope, as we have 2 different controllers, the scopes will be different (I feel so unless you have bound the two controllers
in the same html). So it won't be the correct to use $watch in this
situation.
2) Use a broad cast receiver concept
Advantage : It's preferred as there is no continuous watching required, and triggered only when the value changes
Step 1) In the first controller, register a broadcast as:
.controller('SidebarCtrl', function($scope, typesOfRates) {
$scope.rates = typesOfRates.rateType;
$scope.currentRate = {
selectedRate: 'hourly'
};
$scope.rateTypeChanged = function(rate) {
console.log("Selected goalType, text:", rate.title, "value:", rate.id);
typesOfRates.setRate(rate.id);
//$broadcast(name, args); here name you have to give in a file
//which is commonly accessible like constants.js, just create a
//file and include in you index.html, pass your rates as args
$rootScope.$broadcast(constants_config.TYPE_RATES_CHANGED, rate.id);
}
});
Step 2) Create constants.js file and include in your index.html as:
<!-----Constants Classes---->
<script src="Constants.js"></script>
In constants.js add the following code:
var constants_config = {
TYPE_RATES_CHANGED : "TYPE_RATES_CHANGED",
}
Step 3) Register your listener in the second controller as
.controller('AppCtrl', function($scope, typesOfRates, $state, $rootScope) {
// #CallBack
// Description : Callback function for user details fetched
$scope.$on(constants_config.TYPE_RATES_CHANGED, function(args) {
//Assign the value to a global variable or a scope variable so
//that you can access it throughout your controller
$scope.Rates = typesOfRates.getRate();
//Now the console will work
console.log( typesOfRates.getRate() );
});
});
Further Reference:
- Broadcasts : $broadcast
- Listeners : $on
- Watch : $watch

How to Change Color of background in Angularjs Dynamically

I have a Poller that I have setup that has 2 files which are being queried. When new data has been found I am trying to set the color of my text background in the view but its just not happening.
If someone can solve this issue that would be great I am also welcome to suggestions to improving the structure of the code.
Service:
function Poller($http, $timeout) {
var projectcache = { response: [], calls: 0 };
var msgcache = { response: [], calls: 0 };
var newdata = false;
var msgdata = false;
var msgcolor = {};
var projectcolor = {};
var poller = function () {
$timeout(poller, 10000);
console.log("Begin Poller!");
$http.get('http://localhost/app/controllers/php/getProjects.php')
.then(function(r) {
if (r.data.projects.length > projectcache.response.length) {
newdata = true;
projectcolor = 'green';
} else {
newdata = false;
projectcolor = 'green';
};
angular.copy(r.data.projects, projectcache.response);
console.log("New Data Found: " + newdata);
});
$http.get('http://localhost/app/controllers/php/getMessages.php')
.then(function(m) {
if (m.data.messages.length > msgcache.response.length) {
msgdata = true;
msgcolor = 'green';
} else {
msgdata = false;
msgcolor = 'green';
};
angular.copy(m.data.messages, msgcache.response);
console.log("New Msg Found: " + msgdata);
});
};
poller();
return {
projects: projectcache.response,
messages: msgcache.response,
newdata: newdata,
msgdata: msgdata,
msgcolor: msgcolor,
projectcolor: projectcolor
};
};
View:
<li ng-class="{active: selectTab=='inbox'}" style="background-color:{{msgcolor}};" ng-click="selectTab='inbox'">Inbox</li>
<li ng-class="{active: selectTab=='projects'}" style="background-color:{{projectcolor}};" ng-click="selectTab='projects'">Projects</li>
Controller:
app.controller("taskbarController", ['$scope', 'authData', '$location', 'projectsModal', 'sendMessageModal', 'Poller',
function ($scope, authData, $location, projectsModal, sendMessageModal, Poller) {
$scope.msgcolor = Poller.msgcolor;
$scope.projectcolor = Poller.projectcolor;
}]);
My first thought is to use ng-class for this. I see you already have ng-class handling the display of your 'active' class.
If you'd like to try this approach out, I would:
1. Create css clases for each state/color you want to change to. (Can do this in external css file or between tags you create at the beginning of your page.
.successBackground {
background-color:green;
}
.errorBackground {
background-color:red;
}
Modify your ng-class attributes. Here I am assuming that success means that msgdata=true and error means that msgdata=false
Current html:
<li ng-class="{active: selectTab=='inbox'}" style="background-color:{{msgcolor}};" ng-click="selectTab='inbox'">Inbox</li>
<li ng-class="{active: selectTab=='projects'}" style="background-color:{{projectcolor}};" ng-click="selectTab='projects'">Projects</li>
Updated html:
<li ng-class="{active: selectTab=='inbox', successBackground:msgdata===true, errorBackground:msgdata===false}" ng-click="selectTab='inbox'">Inbox</li>
<li ng-class="{active: selectTab=='projects',successBackground:msgdata===true, errorBackground:msgdata===false}" ng-click="selectTab='projects'">Projects</li>
Now when your msgdata is updated, the successBackground and errorBackground are automatically updated based on the latest msgdata value.
Hope this helps!
#Elevant, the comment option didn't allow me to format my code snippets, so I am replying to your latest comment in this answer post.
I'm not sure if the watcher can listen to just the Poller object or if it'll need to listen to each attribute separately (msgColor, projectColor). In my code snippet here, I'll assume we cannot and we'll need to listen to each individually.
Current code:
$scope.msgcolor = Poller.msgcolor;
$scope.projectcolor = Poller.projectcolor;
Updated with watchers:
$scope.$watch('Poller.msgcolor', function(newValue,oldValue) {
$scope.msgcolor = Poller.msgcolor;
});
$scope.$watch('Poller.projectcolor', function(newValue,oldValue) {
$scope.projectcolor = Poller.projectcolor;
);
Though if you still wanted to look into the option to move $timeout, I would make the following changes (not sure if this matches what you had tried).
In the Poller service definition remove $timeout. Updated snippet:
function Poller($http)
Still in Poller, remove this line:
$timeout(poller, 10000);
In the Controller add $timeout - updated snippet:
app.controller("taskbarController", ['$scope', 'authData', '$location', 'projectsModal', 'sendMessageModal', 'Poller','$timeout'
function ($scope, authData, $location, projectsModal, sendMessageModal, Poller,$timeout)
Then in the controller, you would add:
$timeout(function(Poller) {
Poller.poller();
$scope.msgcolor = Poller.msgcolor;
$scope.projectcolor = Poller.projectcolor;
}, 10000);
I hope this helps, I haven't had a chance to test the code, so you may have to tinker around with it a bit. Let me know how it goes!

I am not able to pass my recent values added in service method to html page

This is my first html page where I need to populate my recently added data:
<div>
<!--I have binded busdomain through controller -->
<swt-tree tree-data="busdomain"</swt-tree>
</div>
This is my child html page which is called under first page and I want to pass the values entered on this page to my parent page. But not getting recent values until I reload the page.
<span ng-show="true"</span>Name</label>
<div class="col-sm-9">
<input focus="" id="name" placeholder="Name" ng-model="busdomain.name" ng-change="domainNameChanged()" required="true">
</div>
<button type="submit" ng-click="addSubTree(createdomain.$valid)" id="btnData">OK</button>
This is my controller, I have called service through controller:
controller('domainController', ['$scope', '$state', 'DomainNameService', function($scope, $state, DomainNameService) {
$scope.activeTab = 1;
$scope.currentBDStatement=[];
$scope.statements=[];
$scope.childBD =[];
$scope.busdomain=[];
<!--It is used when I navigate from parent to child for the first time -->
$scope.busdomain = DomainNameService.getBusDomainName();
$scope.addSubTree = function(val){
//Done some code here
//In $scope.statements I am getting the values which I need to pass on html page
//I am setting the value in service which I got from my child html page
DomainNameService.setBusDomain($scope.statements);
//Here I am calling the get method of my service and able to get the values which i have set.
$scope.busdomain = DomainNameService.getBusDomainName();
//Redirecting to my parent page here I want to show the values which i have set in $scope.busdomain but I am not getting recent added values..
$state.go('BusDomainTree');
}
This is my service.js:
Here I have used getter and setter:
app.factory('DomainNameService', function() {
var busDomain = undefined;
var busDomainValue=[];
setBusDomain:function(busDomName){
this.busDomainValue=busDomName;
},
getBusDomainName: function(){
<!--I am getting the values here which I need to pass to html page -->
return this.busDomainValue;
}
})
Full controller code:
controller('domainController', ['$scope', '$state', 'DomainNameService', function($scope, $state, DomainNameService) {
$scope.activeTab = 1;
$scope.currentDomain = {};
$scope.currentBDStatement=[];
$scope.statements=[];
$scope.childBD =[];
var statementTree = {};
$scope.busdomain=[];
$scope.domain = DomainNameService.getDomainName();//Getting parent name here
$scope.busdomain = DomainNameService.getBusDomainName();
//$scope.busDom = DomainNameService.getBusDomainModel($scope.statements);
$scope.addTree = function(isValid) {
if(isValid) {
var stType = $scope.domain.name;//Getting value from html page of parent
$scope.currentDomain = $scope.getNewDomain(stType,varType);
$scope.statements.push($scope.currentDomain);
//Adding parent name to tree code $scope.statementTree.setNewInput($scope.statements);
$scope.isAdd = false;
DomainNameService.addDomain($scope.domain.name);
$scope.domain.domainName = DomainNameService.getDomainName()[0];
$state.go('DomainTree');
}
}
$scope.getNewDomain = function(stType,varType) {
//passing parent name as json
return {domainNode:[{name:stType}],name:stType, varName:varType};
}
$scope.addbusinessDomain = function() {
$state.go('DomainTree.businessDomain');
}
//This method is called for child
$scope.addSubTree = function(val){
var varType = "busDomain";
var domain=[];
var busDomainName=$scope.busdomain.name;
var parent = DomainNameService.getDomainName()[0];
DomainNameService.addChildBD(busDomainName);
$scope.childBD=DomainNameService.getChildBD();
$scope.currentStatement = $scope.busdomain.name;
$scope.currentBDStatement.push($scope.busdomain.name); $scope.currentDomainName=$scope.getBusDomain($scope.childBD,parent,varType);
$scope.statements.push($scope.currentDomainName);
$scope.statementTree.setNewInput($scope.statements);
DomainNameService.setBusDomain($scope.statements);
$scope.busdomain = DomainNameService.getBusDomainName();
$state.go('BusDomainTree');
}
$scope.getBusDomain = function(stType,parent,varType) {
return {node:[{name:parent}],name:parent, childNode:stType, refType: varType};
}
I am able to fetch values from service but I am not able to populate the recently added value to html page. For that I need to reload the page. Please some one help me out in resolving this issue. Thanks in advance

ui-bootsrap pagination: first page button is not disabled when page loads

I am displaying a list of elements inside a ng-include.
The list of elements comes from the server using $resource query service.
The list is paginated with the ui-bootstrap pagination directive. the server send the pagination informations inside the Json header (properties are named X-MyApp-…) and are intercepted by the query callback function.
here is the html :
<table ng-include src="'partials/tplList.html'" ng-init="listInit = {'type': collec.type, 'offset': 1}" ng-controller="ListCtrl" >
</table>
the tplList.html :
<tbody ng-init="loadList(listInit)"><tr ng-repeat="elm in list">
<td>{{elm.prop1}}</td><td>{{elm.prop2}}</td><td>{{elm.prop3}}</td>
</tr></tbody>
<tfoot><tr><td colspan="4">
<span ng-show="pageCount>1">
<pagination num-pages="pageCount" current-page="currentPage" max-size="10" on-select-page="loadList(collect(listInit, {offset: page}))">
</pagination>
</span>
</td></tr></tfoot>
and the controller:
controller('ListCtrl', ['$scope', 'List', function($scope, List) {
// collect: concatenate the objects before to send it to loadList()
$scope.collect = function (a,b){
var c = {};
for (var att in a) { c[att] = a[att]; }
for (var att in b) { c[att] = b[att]; }
return c;
}
$scope.loadList = function (param) {
$scope.list = List.query(p, function(list, response) {
$scope.currentPage = response("X-MyApp-currentPage");
$scope.pageCount = response("X-MyApp-pagesCount");
console.log($scope.currentPage); // returns 1 when the page loads.
});
}
}])
and the service :
factory('List', function($resource){
return $resource('url/to/the/json/:type', {type:'#type'});
})
everything is working fine except one thing : when the page loads, the first page button ("1") inside the pagination component is not disabled like it should (and the "previous" and "first" buttons are not either). It's not disabled until i click on another page number (which is disabled correctly when selected) and then click back on the first page button.
any idea ?
This happens because ng-include creates a new scope and the model is not modified in your $parent scope.
Try the following code or create a controller that communicates with the parent one.
<pagination num-pages="pageCount" current-page="$parent.currentPage" max-size="10" on-select-page="loadList(collect(listInit, {offset: page}))">
</pagination>
i found a way to make it work:
removed this line from the controller :
$scope.currentPage = response("X-MyApp-currentPage");
and added this one :
$scope.currentPage = 1;
which gives :
controller('ListCtrl', ['$scope', 'List', function($scope, List) {
// collect: concatenate the objects before to send it to loadList()
$scope.collect = function (a,b){
var c = {};
for (var att in a) { c[att] = a[att]; }
for (var att in b) { c[att] = b[att]; }
return c;
}
$scope.currentPage = 1;
$scope.loadList = function (param) {
$scope.list = List.query(p, function(list, response) {
$scope.pageCount = response("X-MyApp-pagesCount");
});
}
}])
apparently the pagination component doesn't need the X-MyApp-currentPage information from the server (and i'm not sure to understand why).

AngularJS watch service value change instead of scope inheritance

I just give a try to AngularJS. I try to do something quite simple but I'd like to do it the good way.
I got a list of items in a table which displays name and quantity for each item.
I have a form under the table.
When I click on an item name from the table I'd like the given item to be updatable through the form.
I achieve to do thing with scope inheritance as in fiddle http://jsfiddle.net/5cRte/1/
View :
<tr ng-repeat="item in items">
<td>{{item.name}}</td>
<td>{{item.quantity}}</td>
</tr>
Controllers :
function ItemListController($scope){
$scope.items = [{name:'item1', quantity:10}, {name:'item2', quantity:5}];
$scope.selectCurrentItem = function(currentItem) {
$scope.currentItem = currentItem;
}
}
function ItemFormController($scope){
$scope.$watch('currentItem', function() {
$scope.item = $scope.currentItem;
});
}
But has I read in some topics, it is not a good practice to couple controllers scopes this way, and preferably I'll wan't to use a service to store variables shared between controllers.
I was able to put a static variable in a service and retrieve it in another controller, but I can't make it updated when clicking on the item from the table, as watch not working on services variable. Have you an hint, for this ?
Thanks in advance
I don't know whether this is optimal but this what I could come up with
angular.module('myApp', []);
angular.module('myApp').factory('myService', function(){
var items = [{name:'item1', quantity:10}, {name:'item2', quantity:5}, {name:'item3', quantity:50}];
var current = {};
return {
getItems: function(){
return items;
},
setCurrentItem: function(item){
current.item = item;
},
removeCurrentItem: function(){
delete current.item;
},
getCurrent: function(){
return current;
}
}
});
function ItemListController($scope, myService){
$scope.items = myService.getItems();
$scope.selectCurrentItem = function(currentItem) {
myService.setCurrentItem(currentItem);
}
}
function ItemFormController($scope, myService){
$scope.current = myService.getCurrent();
}
Demo: Fiddle

Resources