Configuring a service that depends on another service angularjs - javascript

What I need to know is how can I use a service like $http outside of the $get function or is it even possible? My code goes and loads a json file that provides a dictionary that my application makes use of in various ways. I want users to be able to customize this dictionary so I'm using jquery's extend method to allow users to add values to an object and extend the dictionary. The instantiate method in the code below handles all of this. What I'd like to be able to do is configure my service like so
config(['_sys_dictionaryProvider', function(_sys_dictionaryProvider) {
_sys_dictionaryProvider.instansiate('config/dictionary/custom/dictionary.json');
}])
But this requires the $http service to be available at the time of configuration and I don't think it is. If I put the $http service as part of the $get property it will work, as explained here, except then the network has to be queried every time the service is used. Is there any way to use a service in the configuration of another service?
Full code below, let me know if I need to clarify.
app.provider("_sys_dictionary", ['$http',
function ($http) {
var dictionary,
DictionaryService = function () {
this.definitions = dictionary;
this.define = function (what) {
var definitions = this.definitions;
if (what instanceof Array) {
for (var i = 0; i < what.length; i++) {
definitions = definitions[what[i]];
}
return definitions;
}
return this.definitions[what];
};
};
return {
$get: function () {
return new DictionaryService();
},
instansiate: function (path) {
$http.get('config/dictionary/dictionary.json').success(function (data) {
dictionary = data;
$http.get(path).success(function (data) {
jQuery.extend(true, dictionary, data)
});
});
}
};
}
]);

Seeing as I don't believe it is possible to use a service in the configuration stage, since there is no way to guarantee that the service your using itself has been configured, I went this route instead
app.provider("_sys_dictionary", function () {
var dictionary,
DictionaryService = function () {
this.definitions = dictionary;
this.define = function (what) {
var definitions = this.definitions;
if (what instanceof Array) {
for (var i = 0; i < what.length; i++) {
definitions = definitions[what[i]];
}
return definitions;
}
return this.definitions[what];
};
};
return {
$get: [
function () {
console.log(dictionary);
return new DictionaryService();
}
],
instansiate: function (path) {
jQuery.ajax({
url: 'config/dictionary/dictionary.json',
success: function (data) {
dictionary = data;
jQuery.ajax({
url: path,
success: function (data) {
jQuery.extend(true, dictionary, data);
},
async: false
});
},
async: false
});
}
};
});
I ended up using jquery's ajax object and turned async to false since I need the dictionary to be ready before the service gets used. Hope this helps someone. If anyone knows a better way of doing this I'd love to know.

Related

Angular $resource serialize functions too

Ik have a JavaScript object with lots of properties and calculated properties (which are functions), which I want to send to the server.
Now when I call this method
resource.save({ id: vm.allData.id }, vm.allData);
(where vm.allData holds the object) only the properties are serialized, and not the results of the functions.
Is it possible to serialize function results also?
So as an example this little object
function Example() {
var obj = this;
this.propa = 5;
this.propb = 10;
this.total = function () {
return obj.propa + obj.propb;
}
}
I want to serialize property propa, property propb and (calculated) property total
Is that possible?
You need to provide some way to evaluate the values to be saved. You can do this outside of the resource.save() or i.e. use transformRequest to do this on the fly. The general concept is to have service to provide dedicated resource like this:
factory("someService", function ($resource) {
return $resource(
'http://your.url.com/api', {}, {
get: {
method: 'POST',
transformRequest: function(data, headers) {
var toBeSaved = {};
for (key in data) {
if (data.hasOwnProperty(key)) {
if(typeof data[key] == function) {
toBeSaved[key] = data[key]();
} else {
toBeSaved[key] = data[key];
}
}
}
return toBeSaved;
}
}
});
});
Beware the assumption made here is that the functions don't require any arguments. Also note that this is just a draft concept so please refer to the $resource documentation for more details.

Calling a service from within another service in AngularJS

I'm attempting to call a service from within another service, then use the returned object to perform some operations. I keep running into a TypeError: getDefinitions is not a function error, however.
Below is my service is called, the service doing the calling, and my relevant controller code:
definitions.service.js:
'use strict';
angular.module('gameApp')
.factory('definitionsService', ['$resource',
function($resource) {
var base = '/api/definitions';
return $resource(base, {}, {
get: {method: 'GET', url: base}
});
}]);
utilities.service.js:
'use strict';
angular.module('gameApp')
.factory('utilitiesService', ['definitionsService', function(definitionsService) {
return {
description: description,
detail: detail,
severity: severity,
};
function description(account) {
var key = angular.isDefined(getDefinitions().ABC[account.code]) ? account.code : '-';
return getDefinitions().IDV[key].description;
}
function detail(account) {
var key = angular.isDefined(getDefinitions().ABC[account.code]) ? account.code : '-';
return getDefinitions().IDV[key].detail;
}
function severity(account) {
var key = angular.isDefined(getDefinitions().ABC[account.code]) ? account.code : '-';
return getDefinitions().IDV[key].severity;
}
var getDefinitions = function() {
definitionsService.get().$promise.then(function(data) {
return data;
});
};
}]);
controller.js:
'use strict';
angular.module('gameApp')
.controller('AccountsController', AccountsController);
AccountsController.$inject = ['$routeParams', 'customersService', 'utilitiesService'];
function AccountsController($routeParams, playersService, utilitiesService) {
var vm = this;
var playerId = $routeParams.playerId;
var getAccounts = function() {
playersService.getAccounts({
playerId: playerId
}).$promise.then(function(accounts) {
for (var i = 0; i < accounts.length; i++) {
if (angular.isDefined(accounts[i].secCode)) {
accounts[i].code = accounts[i].secCode;
accounts[i].severity = utilitiesService.severity(accounts[i]);
accounts[i].detail = utilitiesService.detail(accounts[i]);
accounts[i].description = utilitiesService.description(accounts[i]);
}
}
vm.accounts = accounts;
});
};
var init = function() {
getAccounts();
};
init();
}
Currently your service returns before your variable gets defined. That means the definition is never reached. So it is declared, as the function executes, but is undefined. Just move your variable definition to the top.
This will only prevent the definition error. Another problem is that your getDefinitions function doesn't return anything but you're calling a property on it. One solution I can think of is using a callback, that gets executed when your data is loaded:
angular.module('gameApp')
.factory('utilitiesService', ['definitionsService', function(definitionsService) {
var data;
reload();
var utils = {
description: description,
detail: detail,
severity: severity,
reload: reload,
loaded: null
};
return utils;
function reload() {
definitionsService.get().$promise.then(function(data) {
data = data;
if (utils.loaded && typeof utils.loaded === "function") {
utils.loaded();
}
});
}
function description(account) {
var key = angular.isDefined(data.ABC[account.code]) ? account.code : '-';
return data.IDV[key].description;
}
}]);
Then in your controller you could use the service like this:
utilitiesService.loaded(function(){
accounts[i].description = utilitiesService.description(accounts[i]);
})
old question but still relevant. To expand on Florian Gl's answer above if you have a service with multiple functions and one or more of those functions requires a "pre-service" function to be called for example to load some resource data in like configuration information move that service call to the top, outside of the nested function (in this case below I am dealing with the promise scenario in JavaScript):
angular.module('gameApp')
.factory('utilitiesService', ['definitionsService', function(definitionsService) {
var myFirstConfigValue = '';
// call any all services here, set the variables first
configurationService.GetConfigValue('FirstConfg')
.then(function (response) {
// set the local scope variable here
myFirstConfigValue = response;
},
function() { });
function myTestFunction() {
// make an ajax call or something
// use the locally set variable here
ajaxService.functionOneTwo(myFirstConfigValue)
.then(response) {
// handle the response
},
function(err) {
// do something with the error
});
}
}]);
Key point to note here is that if you need to load in some data you do that first outside of any other functions inside your service (e.g. you want to load some JSON data).

Sensible approach to callbacks on object prototype methods in JavaScript/jQuery?

Is what I've done below a sensible approach to allow callbacks to run on functions defined in an object's prototype, such that the scope is correct?
I've been wrestling with the correct way to set the value of this when an object's prototype method is the one to run in response to a callback which might originate from an AJAX request or from a click binding or whatever.
Here is a simplified annotated version:
// everything is inside an object which provides the namespace for the app
var namespace = {
// a fairly vanilla object creation routing, which uses the prototype
// approach for defining the functions on the object
newObj : function(params) {
var MyObj = function(params) {
this.property = params.property
};
MyObj.prototype = namespace.ObjPrototype;
return new MyObj(params);
},
// the prototype itself, which defines 2 related functions
ObjPrototype : {
// The first is called to do some form of asynchronous operation
// In this case it is an ajax call
doAsync: function (params) {
$.ajax({
type: "get",
url: params.url,
data: params.data,
dataType: params.datatype,
success: namespace.objClosure(this, "asyncSuccess", ["data"]),
});
// the final line above is the key here - it asks a function (below)
// for a closure around "this", which will in turn run the
// function "asyncSuccess" (defined next) with the argument "data"
},
// This is the actual callback that I want to run. But we can't
// pass this.asyncSuccess to the ajax function above, because the
// scope at execution time is all wrong
asyncSuccess : function(params) {
this.property = params.data;
},
},
// This is the bit I sort of invented, to help me around this problem.
// It returns a function which provides a closure around the object
// and when that returned function is run it inspects the requested
// arguments, and maps them to the values in the JS default
// "arguments" variable to build a parameters object which is then
// passed to the function on the object
objClosure : function(obj, fn, args) {
return function() {
if (args) {
var params = {};
for (var i = 0; i < args.length; i++) {
params[args[i]] = arguments[i];
}
obj[fn](params);
} else {
obj[fn]();
}
}
}
}
Now, obviously the actual target callback MyObj.asyncSuccess needs to know that it's going to get a params object, and what structure it will be, and that knowledge has to be shared by the invoking function MyObj.doAsync, but otherwise this seems to work well.
My question is - am I totally mad? Have I missed something obvious that would solve this problem for me in a simpler/less convoluted way? Am I just too far down the rabbit hole by this stage?
I've read around a lot of questions on SO and they have all addressed part of my question, but I don't seem to have got to the bottom of a generally accepted solution for this. I can't be the only person who's ever wanted to do this :)
Edit
I've accepted the answer below, but you need to read all the comments too for it to come together. Thanks folks for your help!
aren't you over complicating things? see if the below code will help you. i did not completely understand your intent but the below code should help you
function newObj(params) {
function asyncSuccess(params) {
this.property = params.data;
}
function objClosure(obj, fn, args) {
return function() {
if (args) {
var params = {};
for (var i = 0; i < args.length; i++) {
params[args[i]] = arguments[i];
}
obj[fn](params);
} else {
obj[fn]();
}
}
}
this.property = params.property
this.doAsync = function (params) {
console.log('reached async');
$.ajax({
type: "get",
url: params.url,
data: params.data,
dataType: params.datatype,
success: objClosure(this, "asyncSuccess", ["data"]),
});
}
}
var k = new newObj({'property':'xyz'});
k.doAsync();
After seeing the comment from "GameAlchemist" i looked into objClosure function i think we can further improvise by using below code: I am still not sure what the value of this.property or data is to give a proper solution hence just assuming few things
function newObj(params) {
function asyncSuccess(params) {
this.property = params ? params.data : null;
}
function objClosure(args) {
return function() {
if (args) {
var params = {};
for (var i = 0; i < args.length; i++) {
params[args[i]] = arguments[i];
}
asyncSuccess(params);
} else {
asyncSuccess();
}
}
}
this.property = params.property
this.doAsync = function (params) {
console.log('reached async');
$.ajax({
type: "get",
url: params.url,
data: params.data,
dataType: params.datatype,
success: objClosure(["data"]),
});
}
}
Few issues here:
if you are already passing params.data to data i.e. data:params.data how can you again assign the value this.property = params.data? Few things are confusing but i hope the above solution works : )

$resource callback with saved value

I've just started using AngularJS and I love it.
However - I have a need to save an item to my database using $resource and then get back and object containing the values of the newly created item in the database (especially the database-assigned ID).
I've found a few articles describing this - but none of them seems to work for me :(
I have a very simple setup:
var app = angular.module("todoApp", ['ngResource', 'ngAnimate']);
app.factory("TodoFactory", function ($resource) {
return $resource('.../api/todo/:id', { id: '#id' }, { update: { method: 'PUT' }});
});
var todoController = app.controller("TodoController", function ($scope, TodoFactory) {
$scope.todos = [];
init();
function init() {
$scope.todos = TodoFactory.query();
}
$scope.addTodo = function () {
TodoFactory.save($scope.item, function () {
// Success
console.log($scope.item); // <--- HERE'S MY PROBLEM
$scope.todos.push($scope.item);
$scope.item = {};
},
function () {
// Error
});
};
But when I call TodoFactory.save, the $scope.item does not contain the Id-property from the database - only the values it had upon calling save.
How can I get my setup to return the updated object with all the database-generated values?
If somebody could point me in the right direction it would be much appreciated :)
Update: I just went over the source for the API I've been supplied - the save-method doesn't update the object that's inserted.
After I fixed this "minor" issue, peaceman's example worked like a charm.
Sorry for the inconvenience everybody - but thank you very much for the responses! :)
The save method of the TodoFactory won't update the $scope.item, but instead calls the callback function with the saved object as a parameter, that contains the new id.
So you have to replace
$scope.addTodo = function () {
TodoFactory.save($scope.item, function () {
// Success
console.log($scope.item); // <--- HERE'S MY PROBLEM
$scope.todos.push($scope.item);
$scope.item = {};
},
function () {
// Error
});
};
with
$scope.addTodo = function () {
TodoFactory.save($scope.item, function (savedTodo) {
// Success
console.log(savedTodo);
$scope.todos.push(savedTodo);
$scope.item = {};
},
function () {
// Error
});
};
This behaviour is documented at ngResource.$resource
This should solve the problem.
$scope.addTodo = function () {
// ADD IN A VARIABLE FOR THE RESPONSE DATA AS THE PARAMETER TO YOUR SUCCESS CALLBACK FN
TodoFactory.save($scope.item, function (responseItem) {
// Success
console.dir(responseItem); // <--- AND THEN USE IT
$scope.todos.push(responseItem);
$scope.item = {};
},
function () {
// Error
});
};

Can I have multiple instances of a RequireJS Module?

I am obviously missing some concept/understanding and most definitely javascript OO basics!
I am loving using RequireJS, and my web app now looks more like a structured app now rather than a whole heap of crazy code.
I am just struggling to understand how/if the following is possible.
I have a module which acts as a base dataservice module called dataservice_base as follows:
define(['dataservices/dataservice'], function (dataservice) {
// Private: Route URL
this.route = '/api/route-not-set/';
var setRoute = function (setRoute) {
this.route = setRoute;
return;
}
// Private: Return route with/without id
var routeUrl = function (route, id) {
console.log('** Setting route to: ' + route);
return route + (id || "")
}
// Private: Returns all entities for given route
getAllEntities = function (callbacks) {
return dataservice.ajaxRequest('get', routeUrl())
.done(callbacks.success)
.fail(callbacks.error)
};
getEntitiesById = function (id, callbacks) {
return dataservice.ajaxRequest('get', routeUrl(this.route, id))
.done(callbacks.success)
.fail(callbacks.error)
};
putEntity = function (id, data, callbacks) {
return dataservice.ajaxRequest('put', routeUrl(this.route, id), data)
.done(callbacks.success)
.fail(callbacks.error)
};
postEntity = function (data, callbacks) {
return dataservice.ajaxRequest('post', routeUrl(this.route), data)
.done(callbacks.success)
.fail(callbacks.error)
};
deleteEntity = function (id, data, callbacks) {
return dataservice.ajaxRequest('delete', routeUrl(this.route, id), data)
.done(callbacks.success)
.fail(callbacks.error)
};
// Public: Return public interface
return {
setRoute: setRoute,
getAllEntities: getAllEntities,
getEntitiesById: getEntitiesById,
putEntity: putEntity,
postEntity: postEntity,
deleteEntity: deleteEntity
};
});
As you can see, I am referencing dataservices/dataservice, which is actually the core AJAX call mechanism (not shown, but really just basic jQuery ajax call in a wrapper).
What I am trying to do is allow this base dataservice module to be "instanced" as follows (within another module - snippet code only):
define(['dataservices/dataservice_base', 'dataservices/dataservice_base', 'dataservices/dataservice_base'], function (dataservice_profile, dataservice_qualifications, dataservice_subjects) {
// Set the service route(s)
dataservice_profile.setRoute('/api/profile/');
dataservice_qualifications.setRoute('/api/qualification/');
dataservice_subjects.setRoute('/api/subject/');
As you can see, I am trying to include the same dataservice_base(defined above) 3 times, but in the function references, I am trying to refer to each instance by named vars i.e:
dataservice_profile, dataservice_qualifications, dataservice_subjects
.. and of course I am trying be able to set a unique setRoute value for each of those instances to use further on in the module.. whilst leveraging the common calls (get,puts,posts etc).
Obviously I am missing a few things here.. but any help to point me back on the road would be very gratefully received!!
Kind Regards,
David.
I think you need to include your dependency only once and use the new keyword. Possibly you will need to refactor so that the common functions are in a depending module:
define(['dataservices/dataservice'], function (dataservice) {
var dataservice_profile = new dataservice();
var dataservice_qualifications = new dataservice();
var dataservice_subjects = new dataservice();
// Set the service route(s)
dataservice_profile.setRoute('/api/profile/');
dataservice_qualifications.setRoute('/api/qualification/');
dataservice_subjects.setRoute('/api/subject/');
// define needs to return something
return {
profile: dataservice_profile,
qualifications: dataservice_qualifications,
subjects: dataservice_subjects
};
});
Yes, brain-freeze or whatever.. problems of working alone sometimes!
So, as #asgoth mentioned, quite rightly had to clear my mind and think things through a bit!
I ended up with a re-factored dataservice_base module as follows:
define(['dataservices/dataservice'], function (dataservice) {
// Set any class/static vars
// Set the instance function
function dataservice_base(setRoute) {
var self = this;
self.route = setRoute;
console.log('setting route: ' + self.route);
function routeUrl(route, id) {
console.log('** Setting route to: ' + route);
return route + (id || "")
}
self.getAllEntities = function (callbacks) {
return dataservice.ajaxRequest('get', routeUrl())
.done(callbacks.success)
.fail(callbacks.error)
}
self.getEntitiesById = function (id, callbacks) {
return dataservice.ajaxRequest('get', routeUrl(self.route, id))
.done(callbacks.success)
.fail(callbacks.error)
}
self.putEntity = function (id, data, callbacks) {
return dataservice.ajaxRequest('put', routeUrl(self.route, id), data)
.done(callbacks.success)
.fail(callbacks.error)
}
self.postEntity = function (data, callbacks) {
return dataservice.ajaxRequest('post', routeUrl(self.route), data)
.done(callbacks.success)
.fail(callbacks.error)
}
self.deleteEntity = function (id, data, callbacks) {
return dataservice.ajaxRequest('delete', routeUrl(self.route, id), data)
.done(callbacks.success)
.fail(callbacks.error)
}
} // eof instance
return dataservice_base;
}
and of course again as #asgoth mentioned, I only need to of course include one reference to the dataservice_base module, and instance it for my needs as follows:
define(['dataservices/dataservice_base','viewmodels/viewmodel_profile', 'viewmodels/viewmodel_qualifications', 'viewmodels/viewmodel_subjects', 'app/common'], function (dataservice_base, viewmodel_profile, viewmodel_qualifications, viewmodel_subjects, common) {
var dataservice_profile = new dataservice_base('/api/profile/');
var dataservice_qualifications = new dataservice_base('/api/qualification/');
var dataservice_subjects = new dataservice_base('/api/subject/');
// do whatever now with those instance objects...
}
SO.. now all working!
I guess the only other thing I need to do is looking up about cleaning up process to ensure these objects are released.. however there will only ever be a few.. but still..
thanks again #asgoth
Just return a function instead of a object like this
return function(){
return {
// your public interface goes here
};
}
Now you can create new instances of your plugin with new componentName().

Categories