Build fluent service in AngularJS - angularjs

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/

Related

Find and return item in Angular deferred promise

So i have a bit of a confusion with angular promises.
I have a service, storing a list of users
.service('user', ['$q','$http', function ($q, $http) {
var services = {};
var _def_userLsit = $q.defer();
$http.get('/api/acc/getlist').then(function (data) {
_def_userLsit.resolve(data);
})
services.getUserListQ = function () {
return _def_userLsit.promise;
}
return services;
}])
after injecting it, i can load my list like this in a controller:
user.getUserListQ().then(function (promise) {
$scope.userHelper.userList = promise.data;
});
no problem here, got the json in the $scope, then the watchers do their jobs just fine.
Here is the Json format simplified for the question:
obj 1 { id=4, userName="foohuman", $$hashKey="object:14"}
obj 2 { id=444, userName="barhuman", $$hashKey="object:22"}
But i also want a user's name by id, and i need to run that several times ( depend on the post count ).
So my question here is, how can i return a user's name like a function call, from that promised list.
like a normal function would do, like this:
$scope.getUserById = function( id ){
return "foo";
//some magic needed here
}
If i just iterate trough the userHelper.userList, it could be empty if it runs too early, so i need to load that trough a promise, but loading it, iterating trough, then adding a string to the $scope is not the best options, since it can run multiple times, so it can overwrite each other, pretty unpredictably if i store it in a single variable.
So any idea how can i return a nice simple string by the id, and not a promise?
EDIT: Ok, so i can't really return a nice string, because it have to be some kind of callback, but i can process the data from the promise, so i ended up loading user data into a array like this:
user.getUserListQ().then(function (promise) {
var uArr = [];
angular.forEach(promise.data, function ( value, key ) {
uArr[value.id] = value.userName;
})
$scope.userHelper.uArr = uArr;
});
and in the html I can boldly write {{userHelper.uArr[taskData.tOwner]}}.
First of all, you're making promises harder than they should be by using the explicit construction antipattern. Your first code snippet can be replaced with this:
.service('user', ['$q','$http', function ($q, $http) {
var services = {};
var pUserList = $http.get('/api/acc/getlist');
services.getUserListQ = function () {
return pUserList;
};
return services;
}]);
Regarding your question:
So any idea how can i return a nice simple string by the id, and not a promise?
This is the wrong mindset to have. For getting a user by their ID, you need to think in terms of promises. You don't know exactly when the data is going to be ready, so this means that your getUserId method should return a promise. You can write it like this:
services.getUserById = function (id) {
return pUserList.then(function (users) {
return users.filter(function (user) { return user.id === id; })[0];
});
};
in your controller, you can use it like this:
myService.getUserById(4).then(function (user) {
$scope.myUser = user;
});

http post and get service

i have a service as follows
angular.module('starter.service')
.factory('authService', function($http) {
var service = {};
service.GetByUsername = function() {
return $http.get('/js/user.json');
}
return service;
})
i just need to know two things,
1. why its is declaring a object named service ?
2.its a service for getting an object, what changes should i change to add another function to post a object(do it in the same code)? dont remove current functionality.
You are using the factory method to create a service, so you need to create an object and return it (Dependency Injection will make sure this object is only instantiated once so it is used as a singleton).
Whether this object you are creating is called "service" or any other way, it doesn't matter, it will work anyway.
As Shankar shown in his example, adding more methods to your service is as easy as adding more methods to the object you are declaring. To clarify the example, I'll add the argument you want to post, and let whoever is using the service to decide what to do with the returned promise (as you do in GET method):
angular.module('starter.service')
.factory('authService', function($http) {
var service = {};
service.GetByUsername = function() {
return $http.get('/js/user.json');
}
service.PostUser = function(user) {
return $http.post("/url/to/post/user", user);
}
return service;
})
You question is not clear, but you can simply add new function to you factory service,
angular.module('starter.service')
.factory('authService', function($http) {
var service = {};
service.GetByUsername = function() {
return $http.get('/js/user.json');
}
service.PostUser = function() {
var data = {}
$http.post("/js/user.json", data).success(function(data, status) {
})
}
return service;
})

what is the scope of a service in angularjs?

I'm trying angularjs for the first time and created a service that I use to make ajax calls to my application API and retrieve a paginated list. I called it "GridService". The service is injected into a controller and everything works great! Until I tried to use the same service twice inside two different controllers on the same page. To my horror, the second instance of the service overwrites the first (doh)
For example; I render two partials as follows :
<div id="location_areas" class="container tab-pane fade" ng-controller="areasController" ng-init="initialise()">
#include('areas._areas')
</div>
<div id="location_people" class="container tab-pane fade" ng-controller="peopleController" ng-init="initialise()">
#include('people._people')
</div>
and I inject the service into a controller as follows and link to the service properties
angular.module('areasController', [])
.controller('areasController', function($scope, $attrs, $http, AreaService, GridService, HelperService) {
$scope.areas = function() { return GridService.getListing() }
$scope.totalPages = function() { return GridService.getTotalPages() }
$scope.currentPage = function() { return GridService.getCurrentPage() }
$scope.columns = function() { return GridService.getColumns() }
Lastly, my abbreviated service is as simple as
angular.module('gridService', [])
.provider('GridService', function($http) {
var columns = {};
var filters = {};
var listing = [];
var totalPages = 0;
var range;
var currentPage = 1;
return {
/**
* Get the requested data (JSON format) from storage then populate the class properties which
* are bound to equivalent properties in the controller.
*
* #return void
*/
list : function(url,pageNumber,data) {
url = url+'?page='+pageNumber;
for (var key in data) {
if( angular.isArray( data[key] ) )
{
angular.forEach( data[key], function( value ) {
url = url+'&'+key+'[]='+value;
});
}
else
{
url = url+'&'+key+'='+data[key];
}
}
$http({ method: 'GET', url: url })
.then(function(response) {
listing = response.data.data;
totalPages = response.data.last_page;
range = response.data.last_page;
currentPage = response.data.current_page;
// Pagination Range
var pages = [];
for(var i=1;i<=response.data.last_page;i++) {
pages.push(i);
}
range = pages;
});
},
Obviously I have boobed (doh). Is it possible to create this scenario or have I misunderstood angularjs architecture?
Please update your question with the relevant code for your service. Services in Angular are by definition Singletons. They should not have any private state. If they do have state, it should be state that is means to be shared between two controllers.
If you are just making $http requests in your service, it should just return the promise to the calling controllers -- not causing any "overlap".
UPDATE
It looks like you a missing a few lines from your service. It looks like it contains columns, filters, etc.
So this is the problem, it is a singleton. What you should do it break it up into two different classes, the network layer that still makes the AJAX call to get the data -- and a factory that returns a new instance of your Grid configuration.
gridFactory.$inject = ['$http'];
function gridFactory($http) {
return function(url, pageNumber, data) {
// put your for (var key in data) logic here...
return new Grid($http);
}
}
function Grid($http) {
var self = this;
$http({ method: 'GET', url: url })
.then(function(response) {
self.listings = data.listings;
self.totalPages = data.total_pages;
...
// put your grid config here following this pattern, attaching each to
// the 'self' property which is the prototype of the constructor.
}
}
So now the factory will return a new instance of the 'Grid' object for every time you call the factory. You need to call the factory like gridFactory(url, pageNumber, data);

Controlling order of execution in angularjs

I have inherited an angular app and now need to make a change.
As part of this change, some data needs to be set in one controller and then used from another. So I created a service and had one controller write data into it and one controller read data out of it.
angular.module('appRoot.controllers')
.controller('pageController', function (myApiService, myService) {
// load data from API call
var data = myApiService.getData();
// Write data into service
myService.addData(data);
})
.controller('pageSubController', function (myService) {
// Read data from service
var data = myService.getData();
// Do something with data....
})
However, when I go to use data in pageSubController it is always undefined.
How can I make sure that pageController executes before pageSubController? Or is that even the right question to ask?
EDIT
My service code:
angular.module('appRoot.factories')
.factory('myService', function () {
var data = [];
var addData = function (d) {
data = d;
};
var getData = function () {
return data;
};
return {
addData: addData,
getData: getData
};
})
If you want your controller to wait untill you get a response from the other controller. You can try using $broadcast option in angularjs.
In the pagecontroller, you have to broadcast your message "dataAdded" and in the pagesubcontroller you have to wait for the message using $scope.$on and then process "getData" function.
You can try something like this :
angular.module('appRoot.controllers')
.controller('pageController', function (myApiService, myService,$rootScope) {
// load data from API call
var data = myApiService.getData();
// Write data into service
myService.addData(data);
$rootScope.$broadcast('dataAdded', data);
})
.controller('pageSubController', function (myService,$rootScope) {
// Read data from service
$scope.$on('dataAdded', function(event, data) {
var data = myService.getData();
}
// Do something with data....
})
I would change your service to return a promise for the data. When asked, if the data has not been set, just return the promise. Later when the other controller sets the data, resolve the previous promises with the data. I've used this pattern to handle caching API results in a way such that the controllers don't know or care whether I fetched data from the API or just returned cached data. Something similar to this, although you may need to keep an array of pending promises that need to be resolved when the data does actually get set.
function MyService($http, $q, $timeout) {
var factory = {};
factory.get = function getItem(itemId) {
if (!itemId) {
throw new Error('itemId is required for MyService.get');
}
var deferred = $q.defer();
if (factory.item && factory.item._id === itemId) {
$timeout(function () {
deferred.resolve(factory.item);
}, 0);
} else {
$http.get('/api/items/' + itemId).then(function (resp) {
factory.item = resp.data;
deferred.resolve(factory.item);
});
}
return deferred.promise;
};
return factory;
}

Initialising function in Angular filter

I am writing a custom filter which involves needing some data map to be initialised so that the data map is not created everytime the filter is invoked.
I do this:
myModule.filter("myfilter", function($filter) {
return function(letter) {
// ...
var blah = myOtherFunction(letter)
}
}
var myOtherFunction = function() {
// initialise some data
var myData = {
"a":"letterA"
"b":"letterB"
}
return function(letter) {
return myData[letter];
}
}();
This means the file where I define my filter has a utility function which uses a closure to close over data which is initialised once and once only.
I am wondering is there a more angular way to achieve this?
Thanks
In general, data should be manipulated/fetched/sent and shared throught services.
But if the "data" you are referring to are:
1.) static and
2.) specific to the logic of the filter
then I believe it does not fall into the general category of "application data"; it is rather "filter logic" stuff.
As such their place is right in the filter.
(BTW, in order to initialize it only once, you don't need all that complex "calling IIFE returned function" stuff. Just put the data in the filter definition function (see below).)
app.filter("myFilter", function() {
var staticFilterSpecificData = {
"a": "letterA",
"b": "letterB"
};
console.log('Initialized only once !');
return function(letter) {
return staticFilterSpecificData[letter] || 'unknown';
};
});
See, also, this short demo.
If your data are static, just create a new module/service and inject it in your filter :
myModule.filter('myfilter', ['myService', function(myService) {
return function(amount, currency) {
return myService.getLetter("a"); //don't know what you want to do here, using amount or currency I guess ?
};
}]);
myModule.factory('myService', function() {
var service = {};
var myData = {
"a":"letterA"
"b":"letterB"
}
service.getLetter = function (letter) {
return myData[letter];
}
return service;
});
If your data are retrieved asynchronously, follow this post : Asynchronously initialize AngularJS filter

Resources