I have an AngularJS service which should get a JSON object and create three arrays based on differing criteria (all, searchable and has coordinates). These arrays need to be referenced by more than one controller, hence the use of a service.
When I test any of the three arrays the array within the service itself (as below), all three are correctly populated.
However, all three of my arrays are empty when referenced by any controller.
What am I missing here?
app.service('$stationsList', ['$http', function($http){
var stationsList = [],
searchableStations = [],
locatableStations = [];
$http.get('stations.json').then(function(res){ // Grab the JSON list of all stations
[].map.call(res.data || [], function(elm){ // Map all stations...
stationsList = res.data; // Set all stations to 'stationsList'
if(elm.link.indexOf(".xml") > -1) // Check to see if the station is searchable (has a full link)
searchableStations.push(elm); // It does - add the station to 'searchableStations'
if( // Check to see if the station can be checked as the closest station (has coordinates)
isFinite(parseFloat(elm.latitude)) &&
isFinite(parseFloat(elm.longitude))
)
locatableStations.push(elm); // It does - add the station to 'locatableStations'
});
console.log(stationsList);
console.log(searchableStations);
console.log(locatableStations);
});
return{
getList: function(){
return stationsList;
},
setList: function(value){
stationsList = value;
},
getSearchable: function(){
return searchableStations;
},
setSearchable: function(value){
searchableStations = value;
},
getLocatable: function(){
return locatableStations;
},
setLocatable: function(value){
locatableStations = value;
}
};
}]);
Example of how I'm referencing service -
app.controller('searchCtrl', ['$scope', '$http', '$localStorage', '$stationsList', function($scope, $http, $localStorage, $stationsList){
$scope.stationsList = $stationsList.getSearchable(); // Grab a list of all stations
$scope.selectStation = click_selectStation; // Handle clicks of a station within the 'searchCtrl' controller
$scope.localStorage = $localStorage.$default({ // Grab the local storage (so that it can be updated when the user selects a station)
recentStations: [] // Set a default value of '[]' for recentStations in case it doesn't exist
});
}]);
Edit
Derived from the answer posted by PankajParkar below, here is the service that will return the three arrays that I require.
However, my issue here is that every call to a method within the service triggers another async call to $http.get my JSON data. This is exactly what I was trying to avoid by using a service.
My desired outcome is one JSON call per page load, with my 3 arrays being created from that JSON call and then accessible to my controllers as and when required. If a service is not the correct answer, I am certainly open to other suggestions.
app.service('$stationsList', ['$http', function($http){
var searchableStations = [],
locatableStations = [];
/**
* Grab all stations (for the master list)
*/
var getAllStations = function(){
return $http.get('stations.json').then(function(res){ // Grab the JSON list of all stations
return res.data;
});
};
/**
* Grab only searchable stations (those with full links)
*/
var getSearchableStations = function(){
return $http.get('stations.json').then(function(res){ // Grab the JSON list of all stations
[].map.call(res.data || [], function(elm){ // Map all stations...
if (elm.link.indexOf(".xml") > -1) // Check to see if the station is searchable
searchableStations.push(elm); // It is - add the station to 'searchableStations'
});
return searchableStations;
});
};
/**
* Grab only locatable stations (those with coordinates)
*/
var getLocatableStations = function(){
return $http.get('stations.json').then(function(res){ // Grab the JSON list of all stations
[].map.call(res.data || [], function(elm){ // Map all stations...
if(
isFinite(parseFloat(elm.latitude)) &&
isFinite(parseFloat(elm.longitude))
) // Check to see if the station is locatable
locatableStations.push(elm); // It is - add the station to 'locatableStations'
});
return locatableStations;
});
};
return{
getAll: getAllStations,
getSearchable: getSearchableStations,
getLocatable: getLocatableStations
};
}]);
Your current code is failing because you made asynchronous ajax call & accepting value as soon as it made. That's why you are getting your values as undefined.
You need to wait till your ajax gets completed, that could be implemented using returning ajax promise to controller from service. So i'd suggest you to create a new method which will do $http ajax and will return promise from that function & that will execute .then function of controller that called the getSearchableStations. Below snippet will give you an Idea what I wanted to say.
Service
app.service('$stationsList', ['$http', function($http) {
var stationsList = [],
searchableStations = [],
locatableStations = [];
var getSearchableStations = function() {
return $http.get('stations.json').then(function(res) { // Grab the JSON list of all stations
[].map.call(res.data || [], function(elm) { // Map all stations...
stationsList = res.data; // Set all stations to 'stationsList'
if (elm.link.indexOf(".xml") > -1) // Check to see if the station is searchable (has a full link)
searchableStations.push(elm); // It does - add the station to 'searchableStations'
if ( // Check to see if the station can be checked as the closest station (has coordinates)
isFinite(parseFloat(elm.latitude)) &&
isFinite(parseFloat(elm.longitude))
)
locatableStations.push(elm); // It does - add the station to 'locatableStations'
});
console.log(stationsList);
console.log(searchableStations);
console.log(locatableStations);
return locatableStations; //return data from here.
});
};
return {
getList: function() {
return stationsList;
},
setList: function(value) {
stationsList = value;
},
getSearchable: function() {
return searchableStations;
},
setSearchable: function(value) {
searchableStations = value;
},
getLocatable: function() {
return locatableStations;
},
setLocatable: function(value) {
locatableStations = value;
},
//added new function
getSearchableStations: getSearchableStations
};
}]);
Inside you controller you will call service getSearchableStations method that does return promise, You will use .then function that would get called when promise get resolved. Same has been shown below with code.
Controller
app.controller('searchCtrl', ['$scope', '$http', '$localStorage', '$stationsList',
function($scope, $http, $localStorage, $stationsList){
$stationsList.getSearchableStations().then(function(data){
$scope.stationsList = data;
$scope.selectStation = click_selectStation; // Handle clicks of a station within the 'searchCtrl' controller
$scope.localStorage = $localStorage.$default({ // Grab the local storage (so that it can be updated when the user selects a station)
recentStations: [] // Set a default value of '[]' for recentStations in case it doesn't exist
});
}); // Grab a list of all stations
}]);
Related
I build chat function in my web app and i am about to create chat functionality between logged clients. Here is my screen from application to show exactly what i want to solve
Screen of my app
As you can see i got list of online users stored in scope in sidebar. Its created as partial view in my Asp.Net with .cshtml and i render content in "white box" using angular routing.
Problem is i use same controller twice and it creates new scope for each html so i got data in my sidebar, but in my content view i dont have any data. I am thinking about passing my data to rootscope, but i dont know if its good idea.
So my question is. Is there anything how i can clone my data from one controller to another or how i can solve this without changing functionality and if i can keep my views controlled with one controller.
Here is my PrivateChatController.js
(function () {
'use strict';
app.controller('PrivateChatController', ['$rootScope', '$scope', 'SignalRService', '$location', 'PrivateChatService', PrivateChatController]);
function PrivateChatController($rootScope, $scope, SignalRService, $location, PrivateChatService) {
//angular stuff
$scope.online_users = [];
$scope.isChatHidden = false;
$scope.openPrivateChatWindow = function (index) {
// $scope.isChatHidden = true;
angular.forEach($scope.online_users, function (value, key) {
if (index == key) {
$rootScope.currentPrivateChatUser = ({
UserName: value.UserName,
ConnectionId: value.connectionId,
});
$location.path("/details/" + value.UserName);
}
});
};
$scope.closePrivateChatWindow = function (index) {
$scope.isChatHidden = false
};
//signalR stuff
var chatHub = $.connection.chatHub;
$.connection.hub.logging = true;
chatHub.client.foo = function () { };
registerClientMethods(chatHub);
$.connection.hub.start()
.done(function(){ console.log('Now connected, connection ID=' + $.connection.hub.id); })
.fail(function () { console.log('Could not Connect!'); });
function registerClientMethods(chatHub) {
//user object
chatHub.client.newOnlineUser = function (user) {
var newUser = ({
connectionId: user.ConnectionId,
UserName: user.UserName
});
$scope.online_users.push(newUser);
$scope.$apply();
};
//compare scope online users with server list of online users
chatHub.client.getOnlineUsers = function (onlineUsers) {
//loop through scope
angular.forEach($scope.online_users, function (scopeValue, scopeKey) {
//loop through received list of online users from server
angular.forEach(onlineUsers, function (serverListValue, serverListKey) {
if (!(serverListValue.ConnectionId == scopeValue.connectionId)) {
var newUser = ({
connectionId: serverListValue.ConnectionId,
UserName: serverListValue.UserName
});
$scope.online_users.push(newUser);
$scope.$apply();
}
})
})
};
chatHub.client.onUserDisconnected = function (id, user) {
var index = 0;
//find out index of user
angular.forEach($scope.online_users, function (value, key) {
if (value.connectionId == id) {
index = key;
}
})
$scope.online_users.splice(index, 1);
$scope.$apply();
};
}};})();
Consider using services as a layer for data sharing. It should also contain chat related logic, in my opinion controllers should be as thin as possible.
Move chatHub.client.getOnlineUsers function to the service and create getter for users.
Further read
I'm breaking my head on this one.
From my backend I fetch json data for my typeahead input field.
once a value is selected I fetch the full object from the backend by using the unique id that came with the auto complete response.
next thing i do is to push this object into an array and then i itterate over this array to display in the html all fine.
but i would like to test first if the object is already in the array.
for that i'm using array.indexOf property, however for some reason it will never find a match: i hope someone can help me:
here is the code:
the factory:
productServices.factory('Product', ['$resource',
function($resource){
return $resource('http://127.0.0.1:5000/products/:id', {id: '#id'}, {
query: { method:'GET',
isArray:false,
responseType:'json',
cache:true},
update: { method:'PUT' }
fetch product from backend and push to an array:
$scope.onSelect = function ($item, $model, $label) {
$scope.$item = $item
// call the .addProduct service to push the product.get result into a list
acProductService.addProduct(Product.get({ id: $scope.$item.id }));
// this is to retrieve the list
$scope.products = acProductService.getProducts();
// reset field
$scope.selected = '';
};
service to push the data to the array and to list the array (so here on addProduct i would like to test if the object is already in the array)
autocompServices.service('acProductService', function() {
var productList = [];
var addProduct = function(newObj) {
console.log(productList.indexOf(newObj)); ---- here is my issue it will never find the newObj despite i've added it many times
productList.push(newObj);
console.log(productList.indexOf(newObj)); -- does work but i need to check before pushing to the list and not after;
};
var getProducts = function(){
return productList;
};
return {
addProduct: addProduct,
getProducts: getProducts
};
i'm guessing the $resource.get returns something that is unique irrespective of the object retrieved from the back-end is actually unique. any help please.. thanks
I have a issue in sharing data between 2 controllers and 2 views. I have 2 views. I created 2 separate controllers and bind with 2 different views. Now I have 2 share data between 2 controllers so I created a service. Issues is one controller get data from remote source and other controller is consuming that data. But the view that consumes data loads first, so pulled data from remote source is not exactly utilize by first one. Eg.
//My Services
as.service('songGenreService', function () {
var genreList = [];
var addGenres = function (newObj) {
genreList = newObj;
};
var getGenres = function () {
return genreList;
};
return {
addGenres: addGenres,
getGenres: getGenres
};
});
as.controller('SongListController', ['$scope', 'Song', "$stateParams", 'Category', 'Album', 'base64', 'langConversion', 'CONFIG', 'Poet', "songGenreService",
function ($scope, Song, $stateParams, Category, Album, base64, langConversion, CONFIG, Poet, songGenreService) {
$scope.getCategories = function () {
Category.find({
filter: {
fields: ["id", "name"],
where: {
parentId: 2
}
}
}).$promise.then(function (categories) {
$scope.categories = categories;// Here I am giving data to other source.
songGenreService.addGenres(categories);
$scope.genreId = $scope.categories[0].id;
$scope.genreName = $scope.categories[0].name;
});
}();
}
]);
as.controller('SongGenreController', ['$scope', 'Song', "songGenreService",
function ($scope, Song, songGenreService) {
$scope.categories = songGenreService.getGenres();
console.log($scope.categories);
}
]);
Issue is "SongGenreController" loads first because of HTML as it loads first. I wish to populate it when data loads successfully. "songGenreService.getGenres();" doesn't run with remote source.
The way I fixed a similar issue is by using a publish subscribe mechanism.
In your service you can put a publish when genres are added like so:
var addGenres = function (newObj) {
genreList = newObj;
$rootScope.$broadcast('genresUpdated, genreList);
};
then in your two controllers you subscribe to the event :
$scope.$on('genresUpdated', function(event, genreList){
$scope.genres = genreList;
// and other code you want to have triggered when the genreList changes
});
So I didn't get an answer from my last question so I decided to handle this myself.
I created a generic controller like this:
.controller('GenericListController', function () {
// Define this
var self = this;
// Define our list
self.list = [];
// Create our page sizes array
self.pageSizes = [10, 20, 50, 100];
// For filtering and sorting the table
self.pageSize = self.pageSizes[0];
self.predicate = 'name';
self.reverse = false;
self.filter = '';
// For deleting
self.delete = function (e, model) {
// Delete the item
service.delete(model.id);
};
});
very simple as you can see.
Now I was using this by injecting it into my controller like this:
.controller('DashboardController', ['GenericListController', 'CenterService', 'companyId', 'centers', function (Controller, service, companyId, centers) {
// Assign this to a variable
var self = Controller;
}])
In theory everything that is assigned to the GenericListController is now available to the DashboardController. The problem is the line in the generic controller that looks like this:
service.delete(model.id);
Somehow I need to reference my service in the generic controller. I thought that maybe I could create a provider and inject the service reference into the constructor but I am not sure if it being a singleton is an issue, so I need some help.
Is a service / factory / provider a good way to build the GenericListController?
Does a service / factory being a singleton affect anything? If so, can they be created so they are not singletons?
Is there another way to achieve what I am after?
Update 1
So it appears some people are confused....
So if I created a factory that looks like this:
.factory('ListControllerService', function () {
// Default constructor expecting a service
return function (service) {
// Define this
var self = this;
// Define our list
self.list = [];
// Create our page sizes array
self.pageSizes = [10, 20, 50, 100];
// For filtering and sorting the table
self.pageSize = self.pageSizes[0];
self.predicate = 'name';
self.reverse = false;
self.filter = '';
// For deleting
self.delete = function (e, model) {
// Delete the item
service.delete(model.id);
};
};
})
then I create 2 separate controllers that looks like this:
.controller('DashboardController', ['ControllerService', 'CenterService', 'companyId', 'centers', function (Controller, service, companyId, centers) {
// Assign this to a variable
var self = new Controller(service);
self.list = centers;
}])
.controller('CompanyController', ['ControllerService', 'CompanyService', 'ArrayService', 'companies', function (Controller, service, arrayService, centers) {
// Assign this to a variable
var self = new Controller(service);
self.list = companies;
}])
Hopefully you can see that the service I am injecting into the ListControllerService is different for each controller. The only caveat I have with my example is that each "service" must have a delete method (not so difficult because they are all api services).
I hope that explains things better.
The solution I am using on my current project is to write a function that registers a factory.
function CreateMyGenericFactory(factoryName, serviceName)
app.factory(factoryName, [serviceName, function (service){
var GenericListFactory = {
list: [],
pageSizes: [10, 20, 50, 100],
predicate: 'name',
reverse: false,
filter: ''
delete: function(e, model){
service.delete(model.id);
}
}
GenericListFactory.pageSize = GenericListFactory.pageSizes[0];
return GenericListFactory;
}]);
}
Then execute the function in your JS to register a new factory dynamically.
CreateMyGenericFactory('ListFactory', 'ListService');
And use it in your controller.
app.controller('GenericListController', ['ListFactory', function (ListFactory) {
...
console.log(ListFactory.pageSizes.length); // -> 4
ListFactory.delete(e, model);
}];
I also registered the service inside my CreateMyGenericFactory function to further reduce duplication, you may consider doing that as well if each factory has its own service.
The final solution looked like this.
function CreateMyGenericFactory(name)
var factoryName = name + 'Factory'; // e.g. listFactory
var serviceName = name + 'Service'; // e.g. listService
app.factory(factoryName, [serviceName, function (service){
var factory = {
list: [],
pageSizes: [10, 20, 50, 100],
predicate: 'name',
reverse: false,
filter: ''
delete: function(e, model){
service.delete(model.id);
}
}
factory.pageSize = factory.pageSizes[0];
return factory;
}]);
app.service(serviceName, ['$resource', function (resource){
return $resource(Modus[backend] + '/api/'+slug+'s/:id', {
id: '#_id'
});
}]);
}
That way I could register all the Factories and Services I needed without duplicating any code.
CreateMyGenericFactory('user');
// this registered userFactory and userService
Well, it looks like I was nearly there.
I have managed to solve this but I am not sure if it is the best way to do it, but it certainly works.
So my service is very similar to before:
.factory('ListControllerService', function () {
// Default constructor expecting a service
return function (ctrl, service) {
// Define this
var self = ctrl;
// Define our list
self.list = [];
// Create our page sizes array
self.pageSizes = [10, 20, 50, 100];
// For filtering and sorting the table
self.pageSize = self.pageSizes[0];
self.predicate = 'name';
self.reverse = false;
self.filter = '';
// For deleting
self.delete = function (e, model) {
// Delete the item
service.delete(model.id);
};
return self;
};
})
The only thing that changes is that instead of:
self = this;
I now do:
self = ctrl;
ctrl is the controller that is inheriting from this service.
I also return self so that I can bind it to self in the inherited controller.
now my controller looks like this:
.controller('DashboardController', ['ListControllerService', 'CenterService', 'companyId', 'centers', function (Controller, service, companyId, centers) {
// Assign this to a variable
var self = new Controller(this, service);
console.log(this);
console.log(self);
// Assign our centers
self.list = centers;
}])
both console.log output the same which is great.
I am building an SharePoint App using AngularJS and am attempting to define a service that retrieves if the user is an Admin or not. The service itself is successfully logging/working as expected, but I am not sure how to use this in a controller. My end goal is that when a page loads that is tied to a controller, that this service checks if they are an admin or not. From that point, I can do all sorts of magic (ex. redirect, etc.). Here is my service:
// Check if user is an admin
appServices.factory('appAdminCheck', ['$resource', 'appCurrentUserProfile', 'appAdmins', function ($resource, appCurrentUserProfile, appAdmins) {
var userAdmin = [];
appCurrentUserProfile.query(function (usercheck) {
var userID = usercheck.Id;
appAdmins.query(function (admins) {
var admins = admins.value; // Data is within an object of "value", so this pushes the server side array into the $scope array
// Foreach type, push values into types array
angular.forEach(admins, function (adminvalue, adminkey) {
if (adminvalue.Admin_x0020_NameId == userID) {
userAdmin = true;
console.log("I'm an Admin" + userAdmin);
}
});
});
});
return userAdmin;
}]);
Update: Upon closer inspection, I would like to return the array of values, but it keeps stating that the array length is 0. I am sure it is because I am not "returning" properly.
Here is my updated service:
appServices.factory('appAdminCheck', ['$resource', 'appCurrentUserProfile', 'appAdmins', function ($resource, appCurrentUserProfile, appAdmins) {
var userAdmin = [];
var checkUser = function() {
appCurrentUserProfile.query(function (usercheck) {
var userID = usercheck.Id;
appAdmins.query(function (admins) {
var admins = admins.value; // Data is within an object of "value", so this pushes the server side array into the $scope array
// Foreach type, push values into types array
angular.forEach(admins, function (adminvalue, adminkey) {
if (adminvalue.Admin_x0020_NameId == userID) {
userAdmin.push({
isAdmin: 'Yes',
role: adminvalue.Role,
});
}
});
});
});
return userAdmin;
}
return {
checkUser: checkUser
};
}]);
Here is a logging call in a controller:
var test = appAdminCheck.checkUser();
console.log(test);
Seeing as there appears to be some asynchronous actions happening, you'll want to return a promise. You can do this by chaining the then promise resolution callbacks from your other services (assuming they're $resource instances or similar). For example...
appServices.factory('appAdminCheck', function (appCurrentUserProfile, appAdmins) {
return function() {
return appCurrentUserProfile.query().$promise.then(function(usercheck) {
return appAdmins.query().$promise.then(function(admins) {
// this needs to change if admins.value is not an array
for (var i = 0, l = admins.value.length; i < l; i++) {
if (admins.value[i].Admin_x0020_NameId === usercheck.Id) {
return true;
}
}
return false;
});
});
};
});
Then, you can use this promise resolution in your controller, eg
appAdminCheck().then(function(isAdmin) {
// isAdmin is true or false
});