JavaScript service: how to provide a () method, like an object constructor? - javascript

I would like to create a service that will create an interface to AngularJS's $http based on this. I would like to use dependency injection to inject the service without re-writing individual function calls and keeping the code clean. To do this I am providing a common service that creates a variable to the $http object like this:
commonService = function(...) {
return {
...
$http: $http
}
Then I use common.$http everywhere in the code. To change $http from AngularJS $http to my httpService, I need to only change it in one place. httpService looks like this:
function httpService($http, $q, pendingRequests, $interval) {
var get = function(url) {
...
return requestPromise;
};
var service = {
get:get
};
return service;
}
This works for calls to $http.get() but what about calls to $http({method:'get', url:'...'}); ?
Is it possible to provide the () method, which is really httpService()()? Otherwise it would call the AngularJs method httpService().

You can just return a function instead of an object. You can add the properties to your function
function httpService($http, $q, pendingRequests, $interval) {
var get = function(url) {
...
return requestPromise;
};
// Instead of returning an object, return a function
var service = function() {
// do whatever you want here
};
service.get = get;
// Add more properties to service as needed
return service;
}

Related

Simple factory setter/getter in Angular?

I have this factory...
spa.factory("currentPageFactory", function() {
var pageDefinition = {};
pageDefinition.save = function(newPageDefinition) {
pageDefinition.value = newPageDefinition;
}
pageDefinition.read = function() {
return pageDefinition.value;
}
return pageDefinition;
});
...and this controller...
var pageDefinitionController = spa.controller("pageDefinitionController", ["currentPageFactory", function(currentPageFactory) {
currentPageFactory.save("foobar");
}]);
I have tested using the .read() function I created in this factory by including a definition in the factory of pageDefinition.value. I could read the variable using the getter just fine. The problem seems to lie in the setter.
I'm calling these functions like this...
/*Setter Call Example*/
currentPageFactory.save("blah");
/*Getter Call Example*/
this.foobar = currentPageFactory.read();
What am I doing wrong? Why is the setter not working?

How to call a factory method dynamically coming from variable?

I have a service which will return the name of factory. I already injected all the factories into controller. I need to use the variable to call the method inside that factory. I know i can use
if(var == 'factoryname') {
factoryname.method()
}
but i don't want those if conditions because i have number of factories. Is there any way to call a method inside that factory like in java script
window[var]
You should consider storing all of your factories on an object:
var factories = {
factoryA: { method: function() {} },
factoryB: { method: function() {} },
};
var factory = 'factoryA';
factories[factory].method();

http.jsonp and callbacks in javascript

I am working on a calculator that will consider AWS instance costs. I am pulling the data from a .js file on amazon and I would like to read it into an object but i keep getting an error "Uncaught ReferenceError: callback is not defined" .. here is my .js file.
(function() {
var app = angular.module('formExample', []);
var ExampleController = function($scope, $http) {
$scope.master = {};
$scope.update = function(user) {
$scope.master = angular.copy(user);
$scope.GetAws();
};
$scope.reset = function() {
$scope.user = "";
};
function callback(data) {
$scope.aws = data;
}
$scope.GetAws = function() {
var url = "http://a0.awsstatic.com/pricing/1/ec2/linux-od.min.js?callback=callback";
$http.jsonp(url);
};
$scope.reset();
};
app.controller('ExampleController', ['$scope', '$http', ExampleController]);
}());
It is weird that the aws link you are using supports jsonp but it does not take custom callback function name. (Atleast you can look up to find out if the query string they are looking for is callback or not). angular handles it when we provide callback=JSON_CALLBACK it gets translated to angular.callbacks_x which are exposed globally temporarily by angular to handle the request and resolve the promise accordingly. But for this the endpoint must take the callback argument and wrap the response in the same string and function invocation. However this endpoint does not seem to consider it and even without any callback it automatically wraps into default callback function invocation. So you would need to inject $window (Correct DI way) object and set callback function to it and ?callback=callback is irrelevant.
var ExampleController = function($scope, $http, $window) {
$scope.master = {};
//....
$window.callback = function(data) {
$scope.aws = data;
}
$scope.GetAws = function() {
var url = "http://a0.awsstatic.com/pricing/1/ec2/linux-od.min.js?callback=callback";
$http.jsonp(url);
};
$scope.reset();
};
app.controller('ExampleController', ['$scope', '$http', '$window', ExampleController]);
Plnkr
It is because the AWS script is looking to call a function called "callback" on the global scope (outside of Angular). Since your function is within the scope of another (IIFE) function, it cannot be accessed.
What I've done in a case like this is simply put the function in the global scope.
In cases where an application requires some API to have loaded before Angular can do it's magic and has a callback similar to your situation, I have done the following, manually bootstrapping Angular:
index.html
<script src="http://www.example.com/api?callback=myCallbackFunction"></script>
app.js
// callback function, in global (window) scope
function myCallbackFunction() {
// manually bootstrap Angular
angular.element(document).ready(function() {
angular.bootstrap(document, ['myApp']);
});
}
// your IIFE
(function() {
})();
Notice callback should be set in window scope.
So,one solution is like:
$scope.reset = function() {
$scope.user = "";
};
window.callback = function(data) {
$scope.aws = data;
}
$scope.GetAws = function() {
var url = "http://a0.awsstatic.com/pricing/1/ec2/linux-od.min.js?callback=callback";
$http.jsonp(url);
};

Extending an object in javascript/angular

So I have a tree helper object in my angular app that provides a bunch of useful functions for dealing with the many tree structures in the app. This is provided by a factory, so each time some controller (or whatever) asks for a tree helper, it gets its own copy:
angular.module('MainApp').factory('TreeHelperFactory',
function ($http, $q, $filter, DataService) {
var treeHelper = new function ($http, $q, $filter, DataService) {
...
code
...
})
Now I have a category service that provides various category-related functions, including returning a tree of categories and providing ways to manipulate that tree. So thinking like an OO developer, I reckon that the category service is really like a subclass of my tree helper, so I inject a tree helper instance and then attempt to extend that instance with category-specific functions, naively like this:
angular.module('MainApp').provider('CategoryService',
function() {
this.$get = function ($http, $q, $filter, DataService, TreeHelperFactory) {
var catService = TreeHelperFactory;
catService.listCategories = function() {...}
catService.deleteCategory = function(id) {...}
... more code...
return catService;
}
}
);
But this doesn't work. The new properties are not visible when I try to invoke them later, only the tree helper properties of the original factory object. What am I missing and how do I achieve this?
Services in angular.js are singletons, which means each time you inject a service it returns the exact same object, you cannot force it to return a new object each time it is injected.
What you can do is to create a Class function, for example:
angular.module('MainApp')
.factory('TreeHelperFactory',
function ($http, $q, $filter, DataService) {
/**
* TreeHelper constructor
* #class
*/
function TreeHelper() { /* this.init(); */ }
/**
* TreeHelper static class method
*/
TreeHelper.list = function() { /* ... */ };
/**
* TreeHelper instance method
*/
TreeHelper.prototype.init = function() { /*...*/ };
/**
* Returns the class
*/
return TreeHelper;
})
Now you can inject it, instantiate an object and extend it like so:
.factory('CategoryService', function (TreeHelperFactory) {
var catService = new TreeHelperFactory();
catService.listCategories = function() { /*...*/ };
catService.deleteCategory = function(id) { /*...*/ };
return catService;
});
You can also use javascript prototypical inheritance but I think it's an overkill for most cases so keep it simple if you can.
Helper that depends on Angular services is a bad smell for me - almost like its status is more than a helper and it has a service or two in it. Would you be able to rethink this through restructuring their roles and responsibilities?

Returning function from AngularJS factory

I'm trying to understand what the purpose is of the return part of this AngularJS factory method means?
return {
getMessages: getMessages
};
What happens if we added a new method to this factory called getAnotherMessage(), would we need to update this return segment?
myModule.factory('HelloWorld', function($q, $timeout) {
var getMessages = function() {
var deferred = $q.defer();
$timeout(function() {
deferred.resolve(['Hello', 'world!']);
}, 2000);
return deferred.promise;
};
return {
getMessages: getMessages
};
});
factory is a provider constructor:
factory(fn) - registers a service factory function, fn, that will be
wrapped in a service provider object, whose $get property will contain
the given factory function.
Thus when the factory is first loaded by Angular it executes the function that's passed in and stores whatever is returned as the provider.
In other words, the following is run once, and only once- during bootstrapping:
var getMessages = function() {
var deferred = $q.defer();
$timeout(function() {
deferred.resolve(['Hello', 'world!']);
}, 2000);
return deferred.promise;
};
return {
getMessages: getMessages
};
The above gets a reference to the getMessage function and attaches it to the property getMessages inside the returned singleton object.
When the provider is then injected into your code, that returned object is what is passed in giving you access to the HelloWorld.getMessages function (and any other properties in the returned object).
So, yes, if you want to associate another property, such as a function, with the provider (that the factory constructs) you need to include it as a property of the returned singleton object:
return {
getAnotherMessage: function() { ... },
getMessages: getMessages
};
You can also declare an empty object first and add functions into the object
and finally return the object.
myModule.factory('HelloWorld', function($q, $timeout) {
var myobject = {};
myobject.getMessages = function() { ... };
myobject.getAnotherMessages = function() { ... };
return myobject;
});

Categories

Resources