AngularJS Generic List Controller - angularjs

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.

Related

Creating angular base service and sub services

I'm trying to create a general service for dynamic listing objects i angular and for different types of Objects I need slightly different methods for this service. So I thought it would be the best to have a base service and some sub-services. The problem is, that I need to initialize the base service with different Objects depending on sub-service.
So that what I got so far:
Base List-Service (shortened to the relevant)
App.factory('List', ['$q',
function (){
var List = function(Item, searchParams){
this.Item = Item;
this.searchParams = searchParams;
//....
this.nextPage();
};
//.....
List.prototype.nextPage = function () {
//.....
this.Item.find({
//.....
}.bind(this));
};
return List;
}]);
Sub-service of List-Service
App.factory('UserList', [
'User', 'List','$q',
function (User, List) {
UserList = function(){
var searchParams = {
// params Object
};
return new List(User, searchParams);
};
// extend base class:
UserList.prototype.updateUser = function(id){
//.....
}
//....
return UserList;
}]);
Currently just the UserList is loaded, but: Of course it loads every time a new instance, due the new operator when it's called, but I just want one instance. But leaving the new operator throw's an error that this.nextPage(); would be undefined function. Beside this it seems the extension function updateUser is not applied.
So what's the best practice to inherit from other service with passing arguments to parent service in angular?
I gotta work it.
changed sub service to this to inherit proper from base:
App.factory('UserList', [
'User', 'List','$q',
function (User, List) {
var UserList = function(){
var searchParams = {
//.....
};
List.call(this, User, searchParams);
};
// inherit from List service
UserList.prototype = Object.create(List.prototype);
UserList.prototype.updateUser = function(id) {
//.....
};
return UserList;
}
])
;

Array populated witin 'service' but empty when referenced by any 'controller'

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
}]);

Using AngularJS Service to Check if User has Admin Permissions

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
});

AngularJS nested functions in services and dependency injection?

Ok I have two modules which depend upon each other both modules have services, directives, ctrl's etc, now my question is how do i get values assigned in the nested function of the second module's service in the controller of the first service, I have added the dependencies to the first controller but i can't see to get at the nested functions variables to then manipulate them in the ctrl of the first module here's the code(considerably cut down):
angular.module("mainapp", [
"dateSheet",
"bookingApp"
]).controller("AppCtrl", [
"$scope",
"$attrs",
"Booking",
function (scope, source, attributes, AppDataLoader, booking, Booking) {
//HERE I NEED TO BE ABLE TO DO SOMETHING LIKE THIS
var getdaiyrate = function(){
var dumpDailyRates = scope.Booking.getalldates.getrates.dailyPrice
console.log(dumpDailyRates);
}
}
]);
angular.module("bookingApp", ["bookingApp.services",]);
angular.module("bookingApp.services").service("Booking", [
function(){
function getRate(source, dateSheet, dateSheetCtrl, expect, $$childTail, appData) {
var dateValue = $("Date", source).text() || "";
if (!dateValue) {
return null;
}
var dailyPrice = $("DailyPrice", source).text() || "";
var weeklyPrice = $("WeeklyPrice", source).text() || "";
var monthlyPrice = $("MonthlyPrice", source).text() || "";
var isAvailable = $("IsAvailable", source).text() === "1";
var minimumStay = Number($("MinimumStay", source).text());
if (isNaN(minimumStay)) {
minimumStay = DEFAULT_MINIMUM_STAY;
}
return {
date: new Date(dateValue),
dailyPrice: dailyPrice,
weeklyPrice: weeklyPrice,
monthlyPrice: monthlyPrice,
reserved: !isAvailable,
minimumStay: minimumStay
};
}
return {
getalldates: function(source, $scope){
return getRate(source, scope);
}
};
}
]);
The above doesn't work what am i doing wrong....
Could someone please send me in the direction of a decent tutorial that deals with a end to end app using various modules and dependencies??
Chris
You need to inject the service module into the module that you want to use it in. So the first line becomes
angular.module("mainapp", ["dateSheet","bookingApp","bookingApp.services"])
Also i don't see the creation of bookingApp.services so this may also be required
angular.module("bookingApp.services",[]);
and the invocation would be something like this
var dumpDailyRates = Booking.getalldates(sourceParameter, $scope);

Can't get waiting for function to return (with promises?) working in angular controller

I'm trying to get the following findTimelineEntries function inside an Angular controller executing after saveInterview finishes:
$scope.saveInterview = function() {
$scope.interviewForm.$save({employeeId: $scope.employeeId}, function() {
$scope.findTimelineEntries();
});
};
The save action adds or edits data that also is part of the timeline entries and therefore I want the updated timeline entries to be shown.
First I tried changing it to this:
$scope.saveInterview = function() {
var functionReturned = $scope.interviewForm.$save({employeeId: $scope.employeeId});
if (functionReturned) {
$scope.findTimelineEntries();
}
};
Later to this:
$scope.saveInterview = function() {
$scope.interviewForm.$save({employeeId: $scope.employeeId});
};
$scope.saveInterview.done(function(result) {
$scope.findTimelineEntries();
});
And finaly I found some info about promises so I tried this:
$scope.saveInterview = function() {
$scope.interviewForm.$save({employeeId: $scope.employeeId});
};
var promise = $scope.saveInterview();
promise.done(function() {
$scope.findTimelineEntries();
});
But somehow the fact that it does work this way according to http://nurkiewicz.blogspot.nl/2013/03/promises-and-deferred-objects-in-jquery.html, doesn't mean that I can use the same method on those $scope.someFuntcion = function() functions :-S
Here is a sample using promises. First you'll need to include $q to your controller.
$scope.saveInterview = function() {
var d = $q.defer();
// do something that probably has a callback.
$scope.interviewForm.$save({employeeId: $scope.employeeId}).then(function(data) {
d.resolve(data); // assuming data is something you want to return. It could be true or anything you want.
});
return d.promise;
}

Resources