Two controllers with common factory and ng-repeat refresh - javascript

I've problem with AngularJS. Ng-repeat dosn't want to refresh loop when I add new item to JSON from another instance of controller
In first controller I set JSON
(function(){
'use strict';
angular.module('mainApp').controller('ListController', function($scope, FavoriteListService) {
$scope.addToFavorites = function(speaker){
FavoriteListService.setFavorites(speaker);
};
})();
In secound controller I have to display ng-repeat
(function(){
'use strict';
angular.module('mainApp').controller('ShowController', function($scope, FavoriteListService) {
$scope.favoritesList = FavoriteListService.getFavorites();
})();
Factory
(function () {
'use strict';
angular.module('mainApp').factory('FavoriteListService', function () {
var obj = {};
obj.getFavorites = function () {
var favorites = localStorage.getItem('speaker-favorites');
if (favorites == null) {
favorites = {};
} else {
favorites = JSON.parse(favorites);
}
return favorites;
};
obj.setFavorites = function (speaker) {
var favorites = obj.getFavorites();
favorites[speaker.uid] = {firstname: speaker.firstname, name: speaker.name};
localStorage.setItem('speaker-favorites', JSON.stringify(favorites));
};
return obj;
});
})();
Template:
<ul>
<li ng-repeat="(key, fav) in favoritesList">
{{fav.firstname}} {{fav.name}}
</li>
</ul>
Everything is fine, when set & display is in one controller.
If I want to use 2 controllers (or 2 instance of 1 controller) ng-repeat show correctly all items after load page, but when I add new item it doesn't refresh loop and doesn't show new item.
Is any method to fix it?

You either need to change repeater to (and assign that FavoriteListService to $scope variable):
ng-repeat="(key, fav) in FavoriteListService.getFavorites()"
or $watch that favorite list in your controller like that:
$scope.$watch(
function() { return FavoriteListService.getFavorites(); },
function(newValue, oldValue) {
if ( newValue !== oldValue ) {
$scope.favoritesList = newValue;
}
},
true
);
Because when you assign your service method return to scope method it's not being working like a reference.

Create an empty object, and use angular.copy to update the object:
app.factory('FavoriteListService', function () {
var obj = {};
//create empty object
var favorites = {};
obj.getFavorites = function () {
let update = localStorage.getItem('speaker-favorites');
if (update) {
//Use angular copy
angular.copy(update, favorites);
};
return favorites;
};
obj.setFavorites = function (speaker) {
favorites[speaker.uid] = {firstname: speaker.firstname, name: speaker.name};
localStorage.setItem('speaker-favorites', JSON.stringify(favorites));
};
return obj;
});
By using angular.copy, all of the controllers that use the getFavorites function get the same object reference and all of them see the same changes to its contents.
For more information, see AngularJS angular.copy API Reference

Related

Dynamically set data on Angular-chart.js

To be honest I am a bit new to angularjs, so this may be problem with my fundamental understanding of angular, rather than angular-charts.
I have two controllers (PieItemsCtrl and PieCtrl) and I would like to communicate between them by using a factory service (called pieItems)
On the one hand the pieItems works as designed in the PieItemsCtrl.
ie:
$scope.slices = pieItems.list();
Whenever something changes in the pieItems service (ie another element is added), then the HTML is automatically updated via a repeater :
<div ng-repeat="(key, val) in slices">
However in the PieCtrl I have this line, and i would expect the pie chart to update automatically :
$scope.labels = pieItems.labelsItems();
$scope.data = pieItems.data();
It seems to set these data values upon loading/initialisation of the PieCtrl and that's it. Whenever the pieItems data changes these scope values are not updated.
The source of the two controller and factory object are below. And I also made an unworkable fiddle, incase that helps
PieItemsCtrl :
app.controller('PieItemsCtrl', function($scope, $http, $rootScope, pieItems) {
$scope.slices = pieItems.list();
$scope.buttonClick = function($event) {
pieItems.add(
{
Name: $scope.newSliceName,
Percent: $scope.newSlicePercent,
Color: $scope.newSliceColor
}
)
}
$scope.deleteClick = function(item, $event) {
pieItems.delete(item);
}
}
)
PieCtrl :
app.controller("PieCtrl", function ($scope, $timeout, pieItems) {
$scope.labels = pieItems.labelsItems();
$scope.data = pieItems.data();
});
pieItems :
app.factory('pieItems', function() {
var items = [];
var itemsService = {};
itemsService.add = function(item) {
items.push(item);
};
itemsService.delete = function(item) {
for (i = 0; i < items.length; i++) {
if (items[i].Name === item.Name) {
items.splice(i, 1);
}
}
};
itemsService.list = function() {
return items;
};
itemsService.labelsItems = function() {
var a = ['x', 'y'];
for (i = 0; i < items.length; i++) {
a.push(items[i].Name);
}
return a;
};
itemsService.data = function() {
var a = [50,50];
for (i = 0; i < items.length; i++) {
a.push(items[i].Percent);
}
return a;
};
return itemsService;
});
The controller doesn't notice when the value in your factory changes. To include your item-Array in an Angular digest-cycle, tell Angular to $watch that Array.
If you don't want to expose the Array, create a getter:
itemsService.get = function() { return items; }
Then you can include that getter in your $watch expression in your controller:
$scope.$watch(getItems, watcherFunction, true);
function getItems() {
return pieItems.get();
}
The getItems-Function gets called on digest cycle and fires the watcherFunction if the value changed and has the newData as argument. true as 3rd argument creates a deep watch.
function watcherFunction(newData) {
console.log(newData);
// do sth if array was changed
}
For more complex objets, you can use a $watchCollection.

Passing an array from service to controller

I cannot seem to figure out how to pass an array from service to a controller.
I have a simple service
.service('test', function() {
var array = []
return array;
})
And a controller where I call this function when a button is pressed
$scope.testArray = function() {
$scope.test = test.array;
console.log("test: ", $scope.test);
};
I get an error test is undefined. Can anyone explain to me please why this doesn't work and how to fix it? I tried storing that array in a separate object but no luck either. THanks
(See also: this SO question about Angular providers)
A service should put properties directly on this. So instead of
.service('test', function() {
var array = [];
return array;
})
try
.service('test', function() {
this.array = [];
})
(code style notwithstanding; many would suggest preferring function access over direct object access)
.service('test', function() {
var array = [];
this.getArray = function(){
return array;
};
})
Just change test.arraywith test:
JSFiddle
.controller('youCtrl', ['$scope', 'test', function ($scope, test) {
$scope.testArray = function() {
$scope.test = test;
console.log("test: ", $scope.test);
};
});
Add the array variable to your service.
angular.module('moduleName').service('test', function() {
this.array = [];
});
Inject your service into your controller.
angular.module('moduleName').controller('controllerName', function(test) {
$scope.test = test.array;
console.log("test: ", $scope.test);
});

AngularJS controller's variable is not updated

I have problem with below code. I have prices factory which returns object containing prices received from server by websocket. Prices are sent after button Create is clicked. Problem is that main.prices variable is not updated at all. I can check everything by Check button, which confirms this. Prices.data is updated, but this.prices is not, but it refers the same object, so I thought it should be updated as well. Do you have any ideas why below does not work as expected?
angular.module('myApp', ['ngWebSocket'])
.factory('ws', ['$websocket', function($websocket){
var url = 'ws://localhost/websocket';
var ws = $websocket(url);
return ws;
}])
.factory('prices', ['ws', function(ws){
var prices = {
data: [],
clear: function(){
this.data = [];
},
create: function(){
ws.send('send')
}
}
ws.onMessage(function(message){
message = JSON.parse(message.data);
var type = message.type;
if (type == 'new prices'){
prices.data = message.data;
}
});
return prices;
}])
.controller('main', ['prices', function(prices){
this.prices = prices.data;
this.check = function(){
console.log('works ', prices.data);
console.log('not works ', this.prices);
};
this.create = function(){
prices.create();
};
this.stop = function(){
prices.clear();
};
}]);
<div ng-controller="main as main">
{{ main.prices }}
<button ng-click="main.create()">Create</button>
<button ng-click="main.stop()">Stop</button>
<button ng-click="main.check()">Check</button>
</div>
There are a lot of issues with the code you posted (working on a fiddle so i can help rework it) ...
First change :
if (type == 'new prices'){
prices.data = message.data;
}
To:
if (type == 'new prices'){
prices.data.length = 0;
prices.data.push.apply(prices.data,message.data) ;//copy all items to the array.
}
From a readability / maintainability point of view you should just use this.prices vs this.prices.data. It's confusing to map them to other variables, when you can just use prices. Also note that I updated it to use "that" constantly to avoid any type of context this issues.
.controller('main', ['prices', function(prices){
var that = this;
that.prices = prices;
that.check = check;
that.create = create;
that.stop = stop;
function check(){
console.log('works ', that.prices.data);
console.log('not works ', that.prices);
}
function create(){
that.prices.create();
}
function stop(){
that.prices.clear();
}
}]);
To add to the previous response, you also have an issue on the clear():
var prices = {
...
clear: function(){
this.data = [];
},
...
}
when you do the clear with this.data = [] you are actually creating a new empty array an storing that in the this.data prop, and since this is a NEW array, the reference on main controller -> this.prices = prices.data; is still pointing to the old one. If you need to delete elements on the array just use this.data.length = 0 as Nix pointed out for the other method. that will keep all references in sync since you are re using the original array

Angular call service function from within service function, reference to this not working

I'm trying to call a service function within another function of the same service, but seeing some strange behavior. Hopefully this is some obvious mistake I am overlooking. Here's the relevant part of my service:
app.factory('Data', ['$http', function($http) {
var Data = this;
var theProduct = {};
var seletedSku = {};
var uniqueItem = {};
return {
product: function(){
return theProduct;
},
getProduct: function(ext_id){
console.log(Data.product);
console.log(this.product);
},
}
}]);
As you can see, within the getProduct() function, I am simply trying to log the product function just to confirm the reference is working correctly. When getProduct() is called, the first line logs undefined, and the second line logs what I would expect, the product function:
function (){
return theProduct;
}
Why is my reference not working? You can see towards the top of the service I save this to the Data variable. Any ideas?
I'm pasting the full service code below just for reference in case it helps:
app.factory('Data', ['$http', function($http) {
var Data = this;
var theProduct = {};
var seletedSku = {};
var uniqueItem = {};
return {
//return the current product being used in the app
product: function(){
return theProduct;
},
//get a products data from the server and set it as the current product being used by the app
getProduct: function(ext_id){
console.log(Data.product);
console.log(this.product);
return $http.post('get_product', {product_id: ext_id}).success(function(data){
theProduct = data;
//when a product is fetched, set the app's unique item
if(theProduct.unique_item){
Data.setUniqueItem(theProduct.unique_item);
}
else{
Data.setUniqueItem({});
}
});
},
//change the currently selected sku for the app
setSku: function(sku){
if(sku){
selectedSku = sku;
}
else{
//null was passed, meaning, the -- Selected SKU -- option
//was chosen, so reset selectedSku back to an empty object
selectedSku = {};
}
//when sku is set, also need to set current unique item
if(selectedSku.unique_item){
Data.setUniqueItem(selectedSku.unique_item);
}
else{
Data.setUniqueItem({});
}
return selectedSku;
},
//get the currently selected sku for the app
sku: function(){
return selectedSku;
},
//set the current unique item
setUniqueItem: function(item){
//before set a unique item, we need to check if the unique
//item we are setting is the same as the current unique item.
//if it is, we need to set them equal so the new item reflects
//current state of it since it's not repulling the item from the database
if(item.id != uniqueItem.id){
//only change the unique item if they are not the same
//if they are the same, just leave unique item as is
uniqueItem = item;
}
console.log(uniqueItem);
return uniqueItem;
},
//get the current unque item
uniqueItem: function(){
return uniqueItem;
}
}
}]);
Because at the time of the reference, this has no context to itself as an object literal.

AngularJS controller not update model from service

I am new to Angular, but i do not understand why controller updates data from service only in some cases ...
http://plnkr.co/edit/pV8m11Bc1vItDSmQKECS?p=preview
object1 and 2 are correctly updated, but objet3 is not.
Seems that Service.get3() is just fired once.
I understand that Angular watch for object changes, fine for object1 and object2 (that is a reference to object1) by the way, it's great.
But it's not working with object3 ... ok it's not a reference...
The question is : how to build new objects in service ?
Thank you,
Philippe
var app = angular.module('App', []);
app.factory('Service', [
function() {
var object1 = ["TOTO"];
return {
get1: function() {
return object1;
},
get2: function() {
var object2 = object1;
return object2;
},
get3: function() {
var object3 = [];
/*
object1.forEach(function(entry) {
object3.push(entry);
console.log(entry);
});
*/
for (i = 0; i < object1.length; i++) {
object3.push(object1[i]);
}
return object3;
},
set: function(newValue) {
object1.push(newValue);
}
}
}
]);
app.controller('Controller', ['$rootScope', '$scope', 'Service',
function($rootScope, $scope, Service) {
$scope.object1 = Service.get1();
$scope.object2 = Service.get2();
$scope.object3 = Service.get3();
$scope.set = function() {
Service.set("TITI");
}
}
]);
As mentioned the third object is a different array. In order to have it notify your controller of changes you'll probably want to register a watch on it:
http://plnkr.co/edit/EdXVKqe5eaZgROFDG9U9?p=preview
$scope.$watch(Service.get3, function(val) {
$scope.object3 = Service.get3();
});
The first two are bound to object1. The third is bound to a different array altogether.

Categories