I'm not sure what I've done wrong this is how I've setup other factories to share data as far as I can see, but even though the array should be passed by reference and I don't overwrite the array so the reference isn't lost the template never loads with the data I can see coming down from the find all request. Can anyone see what it is that I have/haven't done to get this factory to work and shared between controllers?
I originally set this up not using a service so I know the data returned from the request does work with the view, and if you look at my stubs the data is there (and it looks the same), but the view never updates. If this is something other than a glaring error that I've missed and don't seem to understand I'd appreciate a quick comment as well since I assumed it was the fact objects/arrays are passed by reference that makes this all work.
Contacts Controller
// Stripped down for brevity...
(function () {
'use strict';
function ContactsController(ContactsFactory,
ContactsResource) {
var vm = this;
vm.contacts = ContactsFactory.get();
vm.findAll = function () {
ContactsResource
.all()
.then(function (response) {
ContactsFactory.add(response.contacts); // <-- an array of objects for example: [{id: 5, username: "Richard39", email: "Sanford.Marilyne#Gibson.org",…},…]
});
};
vm.remove = function (contact, index) {
ContactsResource
.remove()
.then(function (response) {
ContactsFactory.remove(index);
});
};
function init() {
vm.findAll();
}
init();
}
ContactsController.$inject = [
'ContactsFactory',
'ContactsResource'
];
angular
.module('app')
.controller('ContactsController', ContactsController);
})();
Contacts Service
// Stripped down for brevity...
(function () {
'use strict';
function ContactsFactory() {
var contacts = [];
function get() {
return contacts;
}
function add(contacts) {
console.log(contacts) // <-- array of objects
angular.forEach(contacts, function(contact) {
console.log(contact) // <-- object
contacts.list.push(contact);
});
console.log(contacts) // <-- objects pushed onto array [Object, Object, Object, Object, Object,…]
// But no update in view, which worked with same resource
// request, prior to switching to factory to share between
// controllers
}
function remove(index) {
contacts.splice(index, 1);
}
// ---
// PUBLIC API.
// ---
return {
get: get,
add: add,
remove: remove
};
}
ContactsFactory.$inject = [];
angular
.module('app')
.factory('ContactsFactory', ContactsFactory);
})();
UPDATE
Using #Rob's suggest makes this work, and I noticed if I added service.contacts as the return value to the getter it also works, and I verified contacts could be removed. So I'm still interested in knowing why this works versus just using an array for contacts like the examples above. Are arrays not passed by reference as I originally thought, and it's only objects? I'd just like to put a name to what I did wrong so it's memorable.
(function () {
'use strict';
function ContactsFactory() {
var service = {
contacts: []
};
function get() {
return service.contacts;
}
function add(contacts) {
angular.forEach(contacts, function(contact) {
service.contacts.push(contact);
});
console.log(contacts);
}
function remove(index) {
contacts.splice(index, 1);
}
return {
contacts: service.contacts, // <-- Rob's suggestion
get: get, // <-- this also works now as well
add: add,
remove: remove
};
}
ContactsFactory.$inject = [];
angular
.module('app')
.factory('ContactsFactory', ContactsFactory);
})();
Related
I need to execute functions of some controllers when my application ends (e.g. when closing the navigator tab) so I've thought in a service to manage the list of those functions and call them when needed. These functions changes depending on the controllers I have opened.
Here's some code
Controller 1
angular.module('myApp').component('myComponent', {
controller: function ($scope) {
var mc = this;
mc.saveData = function(objectToSave){
...
};
}
});
Controller 2
angular.module('myApp').component('anotherComponent', {
controller: function ($scope) {
var ac = this;
ac.printData = function(objects, priority){
...
};
}
});
How to store those functions (saveData & printData) considering they have different parameters, so when I need it, I can call them (myComponent.saveData & anotherComponent.printData).
The above code is not general controller but the angular1.5+ component with its own controller scope. So the methods saveData and printData can only be accessed in respective component HTML template.
So to utilise the above method anywhere in application, they should be part of some service\factory and that needs to be injected wherever you may required.
You can create service like :
angular.module('FTWApp').service('someService', function() {
this.saveData = function (objectToSave) {
// saveData method code
};
this.printData = function (objects, priority) {
// printData method code
};
});
and inject it wherever you need, like in your component:
controller: function(someService) {
// define method parameter data
someService.saveData(objectToSave);
someService.printData (objects, priority);
}
I managed to make this, creating a service for managing the methods that will be fired.
angular.module('FTWApp').service('myService',function(){
var ac = this;
ac.addMethodForOnClose = addMethodForOnClose;
ac.arrMethods = [];
function addMethodForOnClose(idModule, method){
ac.arrMethods[idModule] = {
id: idModule,
method: method
}
};
function executeMethodsOnClose(){
for(object in ac.arrayMethods){
ac.arrMethods[object].method();
}
});
Then in the controllers, just add the method needed to that array:
myService.addMethodForOnClose(id, vm.methodToLaunchOnClose);
Afterwards, capture the $window.onunload and run myService.executeMethodsOnClose()
I have a simple factory in AngularJS:
(function(){
'use strict';
angular
.module('myModule', [])
.factory('myService', service);
function service(){
var products= function(p1, p2, p3, ..., pn) {
var url = "http://something.url/api/action";
var data = {
'p1': p1,
'p2': p2,
...
'pn': pn,
}
// return data
return $http
.post(url, data)
.then(function (response) {
return response.data;
});
}
return {
Products : products
};
}
})();
I use this service inside a controller like this:
myInjectedService
.Products(vm.p1, vm.p1, ... , vm.pn)
.then(successCallbackFn)
.catch(failureCallbackFn);
Each parameter (p1, ..., pn) are used to filter the final result. This works like a charm! But with a little drawback: there are to many accepted arguments for Products and is really difficult to know if I'm sending the right parameters and this sounds a little error prone. What I would is a fluent API for service that make everything more human readable, this would be great:
myInjectedService
.Products()
.FilterById(p1)
.WhereCategoryIs(p2)
...
.WhereSomethingElseIs(pn)
.Send()
.then(successCallbackFn)
.catch(failureCallbackFn);
Previously the task of HTTP call was handled by Products call. Right now Products(), only make an empty query (i.e. {}). Each subsequent FilterByX will enrich the query (i.e. {'productId': 'xxx-yyy-1111'}). Calling Send() will make the real HTTP POST call. This call will use the data builded through various filter applied. How can I do that? I'm playing with prototype but without success.
You can archieve what you want by define a new class and use prototype like this.
In a fluent method, remember to return the object itself.
function service(){
var products = function(url) {
// Define a new Product class
var Products = function() {
this.url = url;
this.data = {};
};
// Add the function
Products.prototype.FilterById = function(id) {
this.data.id = id;
// To make it fluent, return the object itself
return this;
};
Products.prototype.FilterByCategory = function(category) {
this.data.category = category;
return this;
};
Products.prototype.send = function() {
console.log(this.data);
};
// Return an instance of the Products class
return new Products();
};
return {
Products : products
};
};
service().Products().FilterById(1).FilterByCategory("Item").send();
You can read more about it here: https://www.sitepoint.com/javascript-like-boss-understanding-fluent-apis/
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
})
}])
I created a service to share some data between two controllers. The thing is that this controllers set and get some data from this service. But I don't know why when I try to get data, all this variables are not set.
My code is this one:
var appModule = angular.module('app', ['mgcrea.ngStrap'])
// custom service to share/collect data between controllers
// this objects are populated by the controllers
.service('sharedProperties', function () {
this.searchPattern = {
basicFilters: {},
advanceFilters: {}
};
});
// controller for main section
appModule.controller('parentController', function ($scope, $aside, sharedProperties) {
$scope.basicFilters = {
category: 'undef',
masterbrand: {value:'undef', text: 'Any'},
page: 1,
perPage:10,
q:''
};
// populate object in service
$scope.updatePatternSearch = function(newValue, oldValue, scope) {
sharedProperties.searchPattern.basicFilters = 'HI!, I have a value!';
};
$scope.$watch('basicFilters', 'updatePatternSearch', true);
// get variables from service
$scope.search = function() {
// ** PROBLEM **
// firebug says that is:basicFilters: {},
// advanceFilters: {}
// empties????, why??
console.log(sharedProperties.searchPattern);
}
}
});
There are lots of typos and erros in your plnkr code.
I have forked and updated it. It's working fine.
Have a look at this - http://plnkr.co/edit/BVFjufOQFFlhB2gTqIeB?p=preview
I have a 'messages' factory that will query my database for a list of messages.
I'm using the list of messages in two different places. Once to add a message count indicator, and then once to show a list of messages. Since I'm injecting the service into two different controllers, it seems like it's creating two instances of my factory, and hitting the database twice for the list.
How would I set things up to only ask for the list once, and use the list for both display and count purposes in both controllers?
My factory looks like this:
myApp.factory('messagesService', [
'$rootScope',
function($rootScope) {
var messages = [];
function query() {
// Would actually hit the database asyncronously
messages = ['one', 'two', 'three', 'four'];
console.log('query');
$rootScope.$emit('messages.update');
}
function all() {
return messages;
}
return {
query: query,
all: all
}
}
]);
My controllers are using blocks like this to watch for changes:
$rootScope.$on('messages.update', function() {
$scope.messagesCount = messagesService.all().length;
});
But it means i need a messagesService.query(); in each controller for things to be reliable.
So here are a few jsFiddle examples of it as I have things now:
Doesn't work (only updates the header): http://jsfiddle.net/TSLfc/1/
Works but would break if I didn't load the dashboard controller:
http://jsfiddle.net/TSLfc/2/
Works every time, but queries the server twice:
http://jsfiddle.net/TSLfc/3/
Is there a better way to organize my code? Should I build out the messages factory into it's own full module?
Here (Plunkr) is how I would do it:
I have gone back and modified my previous answer, updating with what we discussed in the comments below as well as using promises instead of the timeout as an asynchronous simulation I was showing before (see revision history for reference).
I also removed every variable/function that didn't need to be returned to the controller from the service object, if it doesn't need to be accessed via the controller than it doesn't need to be included on the returned object.
var myApp = angular.module('myApp', []);
myApp.factory('messagesService', [
'$q',
'$rootScope',
'$http',
function ($q, $rootScope, $http) {
var mService = {};
mService.messages = [];
var queryInit = false;
// We don't need to access this function in the controller
// So I am not going to attach to the returned object
var getMessages = function () {
// Stops each controller from getting messages when loaded
if (!queryInit) {
queryInit = true;
// Using the $q promise library we use 'then()' to handle
// What happens after the async call is returned
// The first function parameter is the success/resolve callback
// The second function parameter is the error/reject callback
mService.query().then(function (successResults) {
// Tell all of the controllers that the data has changed
$rootScope.$broadcast('messages.update');
}, function (errorResults) {
console.error(errorResults);
});
}
};
// Used to force an update from the controller if needed.
mService.query = function () {
var deferred = $q.defer();
$http.get('path/to/file.php')
.success(function (data, status, headers, config) {
// assign the returned values appropriately
mService.messages = data;
// this callback will be called asynchronously
// when the response is available
deferred.resolve(data);
})
.error(function (data, status, headers, config) {
// called asynchronously if an error occurs
// or server returns response with an error status.
deferred.reject(data);
});
return deferred.promise;
};
mService.getCount = function () {
return mService.messages.length;
};
mService.all = function () {
return mService.messages;
};
// Initialize the messages
// so we don't need to get the messages in each controller
getMessages();
return mService;
}]);
In your html, on your first controller setup an init function (ng-init="init()") that instantiates the factory:
<div ng-app="myApp">
<div ng-controller="HeaderCtrl" class="header" ng-init="init()">
Messages Count: {{ messageCount }}
</div>
<div ng-controller="DashboardCtrl" class="dashboard">
<ul ng-repeat="message in messages">
<li>{{ message }}</li>
</ul>
<button ng-click="getMessages()">Check for new messages.</button>
</div>
</div>
And in your controllers you just have the $rootScope.$on('messages.update' fn) and you can call manually by calling the services query() function which returns the promise:
myApp.controller('HeaderCtrl', [
'$scope',
'$rootScope',
'messagesService',
function ($scope, $rootScope, messagesService) {
$rootScope.$on('messages.update', function () {
$scope.messageCount = messagesService.getCount();
});
// Manual call, if needed
$scope.getMessageCount = function () {
messagesService.query().then(function (successCallback) {
$scope.messageCount = messagesService.getCount();
});
};
}]);
myApp.controller('DashboardCtrl', [
'$scope',
'$rootScope',
'messagesService',
function ($scope, $rootScope, messagesService) {
$rootScope.$on('messages.update', function () {
$scope.messages = messagesService.all();
});
// Manual call, if needed
$scope.getMessages = function () {
messagesService.query().then(function (successCallback) {
$scope.messages = messagesService.all();
$rootScope.$broadcast('messages.update');
});
}
}]);
You can set cache:true on a $http request. There are numerous ways to data bind within angular without needing to use the $broadcast approach you are using. Also note, $broadcast from a scope will be receievd by all descendent scopes, so no need to inject $rootSCope just for that purpose, can listen on $scope.
Here's one approach that controllers use promise of $http to retrieve data. I used a button click to retrive data for DashControl so can see that request does get cached
myApp.factory('messagesService',function($http) {
return{
query:function query(callback) {
/* return promise of the request*/
return $http.get('messages.json',{ cache:true}).then(function(res){
/* resolve what data to return, can set additional properties of the service here if desired*/
return res.data
}).then(callback);
}
}
});
myApp.controller('HeaderCtrl',function($scope, messagesService) {
messagesService.query(function(messages){
$scope.messagesCount = messages.length;
});
});
myApp.controller('DashboardCtrl', function($scope, messagesService) {
/* use button click to load same data, note in console no http request made*/
$scope.getMessages=function(){
messagesService.query(function(messages){
$scope.messages = messages;
})
}
});
Essentially in this scenario, whatever controller calls the factory service first will generate the data cache
DEMO
I would do it like that:
myApp.factory('messagesService', function() {
var expose = {
messages: []
};
expose.query = function () {
// Would actually hit the database asyncronously
expose.messages = ['one', 'two', 'three', 'four'];
console.log('query');
};
// Initialization
expose.query();
return expose;
}
);
And in your controllers:
$scope.messagesCount = messagesService.messages.length;
Model with broadcasting and pre-hitting database looks heavy for me.
So here is code, that can be embedded in service:
var sv = this;
var deferred = sv.$q.defer();
if (sv._running) {
return sv._running;
}
sv._running = deferred;
It based on reusing promise. To make it query database once - just don't set sv._running to false and it will always return first obtained result.