Refresh controller when factory data updated - angularjs

I have a Factory:
function PeriodDataService (APIService) {
var periodData = {}
periodData.refresh = function(user) {
APIService.query({ route:'period', id: user._id }, function(data) {
periodData.orders = data[0].orders
})
}
periodData.orders = []
periodData.preiod = 1
return periodData
}
angular
.module('app')
.factory('PeriodDataService', PeriodDataService)
And some controllers...for example this one, which use the factory data
function ProductionCtrl ($scope, PeriodDataService) {
$scope.board = PeriodDataService.board
$scope.period = PeriodDataService.period
}
angular
.module('loop')
.controller('ProductionCtrl', ProductionCtrl)
When I call the refresh, the Controlles dont update there data. Whats the reason?
PeriodDataService.refresh(user)
Thank you!

The problem is that, when you call refresh and your service does
periodData.orders = data[0].orders
your service is changing the periodData.orders property to point to a different array than the one it was set to point to when you initialized it (via periodData.orders = []).
This breaks the connection between your controller and the data array in your service because your controller was set up to point to the original array when you did
$scope.orders = PeriodDataService.orders
(which I don't actually see in your sample code but which I assume you're doing somewhere).
This simply set $scope.orders equal to the same pointer as periodData.orders, thus pointing to the original array in memory, and allowing the controller to see changes to that array.
When your service changes periodData.orders to a different pointer, nothing changes the pointer value of $scope.orders, so it's still pointing to the original array, which hasn't changed.
There are different ways you can fix this.
Approach 1
You could have your service .push() the new data into periodData.orders rather than set periodData.orders equal to the returned data array. So your refresh method would look like this:
periodData.refresh = function(user) {
APIService.query({ route:'period', id: user._id }, function(data) {
periodData.orders.length = 0; // empty current orders array
// push the returned orders data into orders array
data[0].orders.forEach(function (item, index) {
periodData.orders.push(item);
});
});
};
As long as you just add to or remove from the original array, and don't redefine it (don't ever do another periodData.orders =), it should work fine.
Approach 2
Alternatively, you could create a new object to hold the orders array (and any other data elements you want to expose) and have a $scope property pointing to that object rather than to the array itself.
So your service would look like this:
function PeriodDataService (APIService) {
var periodDataSvc = {};
// create data object to hold service data
periodDataSvc.periodData = {
orders: [],
period: 1
};
periodDataSvc.refresh = function(user) {
APIService.query({ route:'period', id: user._id }, function(data) {
periodDataSvc.periodData.orders = data[0].orders
})
}
return periodDataSvc;
}
with the orders array now one level deeper at periodDataSvc.periodData.orders.
And you would have a $scope property pointing to the periodData object rather than to the orders array:
function ProductionCtrl($scope, PeriodDataService) {
$scope.periodData = PeriodDataService.periodData;
}
So in your service, when you set the orders array equal to the returned array from your APIService, the controller will see that change because it's watching the periodData object, and a property on that object has changed.
And of course, since you changed the $scope property, any markup that was referencing the orders array would also then need to change to reference periodData.orders instead. For example:
<div ng-repeat="order in periodData.orders">{{order.id}}: {{order.item}}</div>
Here's a fiddle showing both approaches.

I used $rootScope in the factory and $scope.$on in the controller to solve this.
When I change the factory, i use $rootScope.$broadcast to tell the controller that I change it.
.factory('dataFactory', ['$http', '$rootScope', function ($http, $rootScope) {
var dataFactory = {
stock: null,
getStock: getStock
}
function getStock() {
$http.get("/api/itemfarmacia/").then(function success(res) {
dataFactory.stock = res.data;
$rootScope.$broadcast('dataFactory.stock');
}, function error(err) {
onsole.log("Bad request");
})
}
return dataFactory;
}])
and in the controller
.controller('atencion', ["$scope", "$state", "dataFactory", function ($scope, $state, dataFactory) {
$scope.stock = dataFactory.stock;
dataFactory.getStock(); //wherever you execute this, $scope.stock will change
$scope.$on('dataFactory.stock', function () {
$scope.stock = dataFactory.stock; //Updating $scope
})
}])

Related

Make data from factory http.get accessible to entire controller AngularJS

I am trying to give access to a json file that contains config information for my project (things like rev number, project name, primary contact, etc) I created a factory that retrieves the json file using http.get, I can then pull that data into my controller but I am unable to access it from anywhere in the controller.
I did not write the factory, I found it as an answer to another person's question and it is copied almost entirely so if it not the right way to accomplish what I am trying to do please correct me.
here is the factory:
app.factory('configFactory', ["$http", function($http) {
var configFactory = {
async: function() {
// $http returns a promise, which has a then function, which also returns a promise
var promise = $http.get('assets/json/config.json').then(function(response) {
// The then function here is an opportunity to modify the response
console.log(response.data.config);
// The return value gets picked up by the then in the controller.
return response.data.config;
});
// Return the promise to the controller
return promise;
}
};
return configFactory;
}]);
and here is my controller:
app.controller('footerController', ['$scope', '$rootScope', 'configFactory', function footerController($scope, $rootScope, configFactory) {
var body = angular.element(window.document.body);
$scope.onChange = function(state) {
body.toggleClass('light');
};
configFactory.async().then(function(d) {
$scope.data = d;
// this console log prints out the data that I am trying to access
console.log($scope.data);
});
// this one prints out undefined
console.log($scope.data);
}]);
So essentially I have access to the data within the function used to retrieve it but not outside of that. I can solve this with rootScope but I am trying to avoid that because I think its a bandaid and not a proper solution.
Any help would be great but this is my first experience with http.get and promises and all that stuff so a detailed explanation would be very much appreciated.
[EDIT 1] The variables from the config file will need to be manipulated within the web app, so I can't use constants.
Don't assign your response data to scope variable , create a property in your factory itself and assign the response to this property in your controller when your promise gets resolved.This way you will get the value in all the other controllers.
I have updated your factory and controller like below
app.factory('configFactory', ["$http", function($http) {
var configFactory = {
async: function() {
// $http returns a promise, which has a then function, which also returns a promise
var promise = $http.get('assets/json/config.json').then(function(response) {
// The then function here is an opportunity to modify the response
console.log(response.data.config);
// The return value gets picked up by the then in the controller.
return response.data.config;
});
// Return the promise to the controller
return promise;
},
config:'' // new proprety added
};
return configFactory;
}]);
app.controller('footerController', ['$scope', '$rootScope', 'configFactory', function footerController($scope, $rootScope, configFactory) {
var body = angular.element(window.document.body);
$scope.onChange = function(state) {
body.toggleClass('light');
};
configFactory.async().then(function(d) {
// $scope.data = d;
configFactory.config=d;
// this console log prints out the data that I am trying to access
console.log($scope.data);
});
// this one prints out undefined
console.log($scope.data);
}]);
Have you looked into using angular constants? http://ilikekillnerds.com/2014/11/constants-values-global-variables-in-angularjs-the-right-way/ You can leverage them as global variables accessible from any controller without the ramifications of assigning the values to rootScope

How to setup data binding between Factory service and $scope value in particular controller?

I want automatically refresh $scope.variable in both controllers to new value if data.variable in SharedFactory was changed:
.controller('FirstController', function($scope, SharedFactory) {
$scope.variable = SharedFactory.getVal();
})
.controller('SecondController', function($scope, SharedFactory) {
$scope.variable = SharedFactory.getVal();
SharedFactory.setVal("test string 2");
})
.factory("SharedFactory", function () {
var data = { // all variables by default
variable : 'test string'
};
return {
getVal: function () {
return data.variable
},
setVal: function (i) {
data.variable = i;
}
}
});
http://plnkr.co/edit/b1RNcl6Pz2iuRr2t2Q9x?p=preview
So at this example correct result must be "test string 2" in both controllers. How to do that?
Easiest (and possibly more efficient) would be to have a reference to SharedFactory.data directly in your controllers - rather than to SharedFactory.data.variable. That way, when the value of data.variable changes it would change in all controllers as you reference the data-variable rather than the specific value. Using primitives is generally not reccomended.
Another solution would be to use $scope.$watch in your controllers, and just watch for changes on the value and update the local-variable when it changes.
Because you are using primitive variable instead of using object so once you set it you actually lose your reference to original object, so instead of returning data object value (which is primitive) you can return all data object...
getVal: function () {
return data;
}
Here is update plunker...

Fetching an object from a service which hasn't yet been created

Folks:
I have 2 controllers, ctrlA and ctrlB - both unrelated to each other but are within the same page.
ctrlA queries an end point and returns a json object tags, which is then passed to a service method MyService.saveTags(tags) to store the object.
ctrlB then needs to populate a $scope variable $scope.tags by fetching the tags object created via ctrlA.
The service:
.factory('MyService', function($http, $q, $window) {
var myserviceFactory = {};
var savedTags = {};
// ..other methods..
myserviceFactory.saveTags = function(tags) {
if(!savedTags.tags){
console.log('saving tags..');
savedTags.tags = tags;
}
};
myserviceFactory.getSavedTags = function() {
console.log('returning tags..');
return savedTags.tags;
};
return myserviceFactory;
})
This issue appears to be ctrlB gets called first, so when $scope.savedTags = MyService.getSavedTags(); runs, it returns undefined.
Question: Angular n00b here - what would be the best way to fetch the tags after ctrlA has populated the object?
You can use a watch on the getSavedTags service method inside your ctrlB to know when is it populated. Something like
$scope.$watch(function() { return MyService.getSavedTags() },function(newValue) {
if(newValue) {
$scope.savedTags = newValue;
}
});

Angularjs service to manage dataset across multiple controllers

Basically the core of my app centers around a set of data retrieved from the server via a $http request. Once the data is available to the client (as an array of objects) I require it for multiple views and would like to maintain it's state between them, for example, if it has been filtered I would like only the filtered data to be available in the other views.
Currently I have a basic service retrieving the data and am then managing the state of the data (array) in an app-wide controller (see below). This works Ok but it is beginning to become a mess as I try to maintain the array length, filtered status, visible / hidden objects across controllers for each view as I have to keep a track of currentVenue etc in the app-wide controller. Note: I am using ng-repeat in each view to show and filter the data (another reason I would like to just have it filtered in a central spot).
Obviously this is not optimal. I assume I should be using a service to maintain the array of venue objects, so it would contain the current venue, current page, be responsible for filtering the array etc. and just inject it into each controller. My question is, how can I set up a service to have this functionality (including loading the data from the server on start; this would be a good start tbh) such that I can achieve this an then bind the results to the scope. ie: something $scope.venues = venues.getVenues and $scope.current = venues.currentVenue in each views controller.
services.factory('venues', function ($http, $q) {
var getVenues = function() {
var delay = $q.defer();
$http.get('/api/venues', {
cache: true
}).success(function (venues) {
delay.resolve(venues);
});
return delay.promise;
}
return {
getVenues: getVenues
}
});
controllers.controller('AppCtrl', function (venues, $scope) {
$scope.venuesPerPage = 3;
venues.getVenues().then(function (venues) {
$scope.venues = venues;
$scope.numVenues = $scope.venues.length;
$scope.currentPage = 0;
$scope.currentVenue = 0;
$scope.numPages = Math.ceil($scope.numVenues / $scope.venuesPerPage) - 1;
}
});
Sorry for the long wording, not sure how to specify it exactly. Thanks in advance.
The tactic is to take advantage of object references. If you move your shared data to an object, then set that object to $scope, any change on $scope is directly changing the service object since they are the same thing ($scope is referencing the service).
Here's a live sample demonstrating this technique (click).
<div ng-controller="controller-one">
<h3>Controller One</h3>
<input type="text" ng-model="serv.foo">
<input type="text" ng-model="serv.bar">
</div>
<div ng-controller="controller-two">
<h3>Controller Two</h3>
<input type="text" ng-model="serv.foo">
<input type="text" ng-model="serv.bar">
</div>
js:
var app = angular.module('myApp', []);
app.factory('myService', function() {
var myService = {
foo: 'abc',
bar: '123'
};
return myService;
});
app.controller('controller-one', function($scope, myService) {
$scope.serv = myService;
});
app.controller('controller-two', function($scope, myService) {
$scope.serv = myService;
});
I threw this together quickly as a starting point. You can restructure factory any way you want. The general idea is all data in scope has now been moved to an object in factory service.
Instead of resolving the $http with just the response array, resolve it with a much bigger object that includes the array from server. Since all data is now in an object it can be updated from any controller
services.factory('venues', function ($http, $q) {
var getVenues = function(callback) {
var delay = $q.defer();
$http.get('/api/venues', {
cache: true
}).then(function (response) {
/* update data object*/
venueData.venues=response.data;
venueData.processVenueData();
/* resolve with data object*/
delay.resolve(venueData);
}).then(callback);
return delay.promise;
}
var processVenueData=function(){
/* do some data manipulation here*/
venueData.updateNumPages();
}
var venueData={
venuesPerPage : 3,
numVenues:null,
currentVenue:0,
numPages:null,
venues:[],
updateNumPages:function(){
venueData.numPages = Math.ceil(venueData.numVenues / venueData.venuesPerPage) - 1;
},
/* create some common methods used by all controllers*/
addVenue: function( newVenue){
venueData.venues.push( newVenue)
}
}
return {
getVenues: getVenues
}
});
controllers.controller('AppCtrl', function (venues, $scope) {
venues.getVenues(function (venueData) {
/* now have much bigger object instead of multiple variables in each controller*/
$scope.venueData=venueData;
})
});
Now in markup reference venueData.venues or venueData.numPages
By sharing methods across controllers you can now simply bind a form object with ng-model's to a button that has ng-click="venueData.addVenue( formModel)" (or use ng-submit) and you can add a new venue from any controller/directive without adding a bit of code to the controller

AngularJS - refresh list after adding new record to collection / db

How do I update/refresh my $scope.list when a new record is added to the db/collection - storage.set() method - please see comment in the code.
Please see code below.
angular.module("app", [])
.factory('Storage', function() {
var storage = {};
storage.get = function() {
return GetStuffHere();
}
storage.set = function(obj) {
return SetStuffHere(obj);
}
return storage;
})
.controller("MainCtrl", function($scope, Storage) {
$scope.addStuff = function(){
var obj = {
"key1" : "data1",
"key2" : "data2"
};
Storage.set(obj);
// update $scope.list here, after adding new record
}
$scope.list = Storage.get();
});
Here's an approach that stores the received data in the service as an array. It uses promises within the service to either send the previously stored array (if it exists) or makes an HTTP request and stores the response. Using promise of $http, it returns the newly stored array.
This now allows sharing of the stored array across other controllers or directives. When adding, editing, or deleting, it is now done on the stored array in the service.
app.controller('MainCtrl',function($scope, Storage){
Storage.get(function(data){
$scope.items=data
});
$scope.addItem=function(){
Storage.set({name: 'Sue'});
}
})
app.factory('Storage', function($q,$http) {
var storage = {};
storage.get = function(callback) {
/* see if already cached */
if( ! storage.storedData){
/* if not, get data from sever*/
return $http.get('data.json').then(function(res){
/* create the array in Storage that will be shared across app*/
storage.storedData=res.data;
/* return local array*/
return storage.storedData
}).then(callback)
}else{
/* is in cache so return the cached version*/
var def= $q.defer();
def.done(callback);
defer.resolve(storage.storedData);
return def.promise;
}
}
storage.set = function(obj) {
/* do ajax update and on success*/
storage.storedData.push(obj);
}
return storage;
})
DEMO
It's not 100% clear what you want to do, but assuming the storage is only going to update when the user updates it (i.e. there's no chance that two users in different locations are going to be changing the same stuff), then your approach should be to either:
Return a promise containing the newly stored object from the storage service after it's completed, and use .then(function() {...}) to set the $scope.list once it's complete.
You would want to take this approach if the storage service somehow mutates the information in a way that needs to be reflected in the front-end (for example an id used to handle future interaction gets added to the object). Note that $http calls return a promise by default so this isn't much extra code if you're using a web service for storage.
Just add the object to the list on the line after you call it with $scope.list.push(obj)
If you have something that changes on the server side without input from that particular client, then I would look into using a websocket (maybe use socket.io) to keep it up to date.
Solution below will work. However, I am not sure if it is best practice to put this in a function and call when needed (within MainCtrl):
i.e:
On first load
and then after new item added
.controller("MainCtrl", function($scope, Storage) {
$scope.addStuff = function(){
var obj = {
"key1" : "data1",
"key2" : "data2"
};
Storage.set(obj);
// rebuild $scope.list after new record added
$scope.readList();
}
// function to bind data from factory to a $scope.item
$scope.readList = function(){
$scope.list = Storage.get();
}
// on first load
$scope.readList();
});
You have to use
$scope.list = Storage.get;
and in template you can then use i.e.
<ul>
<li ng-repeat="item in list()">{{whateverYouWant}}</li>
</ul>
With this approach you will always have the current state of Storage.get() on the scope
couldn't
return SetStuffHere(obj)
just return the updated list as well? and assign that:
$scope.list = Storage.set(obj);
If this is an API endpoint that returns the single inserted item you could push() it to the $scope.list object.
but maybe I'm missing something you are trying to do...
Updating your backend/Factory stuff is a basic Angular binding done by calling a set/post service. But if you want to automatically refresh your controller variable ($scope.list) based on changes occuring in your factory then you need to create a pooler like function and do something like :
.run(function(Check) {});
.factory('Storage', function() {
var storage = {};
var Check = function(){
storage = GetStuffHere();
$timeout(Check, 2000);
}
// set...
Check();
return storage;
})
.controller("MainCtrl", function($scope, Storage) {
$scope.list = Storage.storage;

Resources