Is it a good idea to communicate between services? - javascript

I was thinking of using one service to store variables and call another service which stores the functions that call the web API that would be called by service1 - would this be a bad idea?
For example, I have a controller that only gets the values from Service1:
(function() {
'use strict';
angular
.module('app.event')
.controller('Controller1', Controller1);
Controller1.$inject = ['service1', '$stateParams'];
/**
* Controller 1
* #constructor
*/
function Controller1(service1, $stateParams) {
// Declare self and variables
var vm = this;
vm.number = 0;
init();
/**
* Initializes the controller
*/
function init() {
service1.refreshCount($stateParams.id);
vm.number = service1.getCount();
}
}
})();
Below is Service 1, which only stores variables and a reference to a function which is in Service2:
(function() {
'use strict';
angular
.module('app.event')
.factory('service1', service1);
service1.$inject = ['service2'];
/**
* The event index service
* #constructor
*/
function service1(service2) {
// Declare
var count = 0;
// Create the service object with functions in it
var service = {
getCount: getCount,
setCount: setCount,
refreshCount: refreshCount
};
return service;
///////////////
// Functions //
///////////////
/**
* Refreshes the count value
*/
function refreshCount(id) {
service2.getCounts(id).then(
function (response) {
setCount(response.data.Count);
});
}
/**
* Returns the count value
*/
function getCount() {
return count;
}
/**
* Updates the count
* #param {int} newCount - The new count value
*/
function setCount(newCount) {
count = newCount;
}
}
})();
And below would be a part of Service2, where the function is called from Service1:
// Gets a count from the DB
getCounts: function(id) {
$http({ url: APP_URLS.api + 'WebApiMethodName/' + id, method: 'GET' }).then(function (response) {
return response.data.Count;
});
},
etc : function() {},
etc : function() {}
Is this a good idea, or is there something bad about communicating between services in this way?

Related

angular/jasmine: Error: Expected spy to have been called

The code exists here https://review.openstack.org/#/c/418828/ but I will go into more detail below:
I am writing tests for this particular piece of code: https://review.openstack.org/#/c/418828/26/openstack_dashboard/static/app/core/network_qos/qos.service.js
(function() {
"use strict";
angular.module('horizon.app.core.network_qos')
.factory('horizon.app.core.network_qos.service', qosService);
qosService.$inject = [
'$filter',
'horizon.app.core.openstack-service-api.neutron',
'horizon.app.core.openstack-service-api.userSession'
];
/*
* #ngdoc factory
* #name horizon.app.core.network_qos.service
*
* #description
* This service provides functions that are used through the QoS
* features. These are primarily used in the module registrations
* but do not need to be restricted to such use. Each exposed function
* is documented below.
*/
function qosService($filter, neutron, userSession) {
var version;
return {
getPolicyPromise: getPolicyPromise,
getPoliciesPromise: getPoliciesPromise
};
/*
* #ngdoc function
* #name getPoliciesPromise
* #description
* Given filter/query parameters, returns a promise for the matching
* policies. This is used in displaying lists of policies. In this case,
* we need to modify the API's response by adding a composite value called
* 'trackBy' to assist the display mechanism when updating rows.
*/
function getPoliciesPromise(params) {
return userSession.get().then(getQoSPolicies);
function getQoSPolicies() {
return neutron.getQoSPolicies(params).then(modifyResponse);
}
function modifyResponse(response) {
return {data: {items: response.data.items.map(modifyQos)}};
function modifyQos(policy) {
policy.trackBy = policy.id;
policy.apiVersion = version;
policy.name = policy.name || policy.id;
return policy;
}
}
}
/*
* #ngdoc function
* #name getPolicyPromise
* #description
* Given an id, returns a promise for the policy data.
*/
function getPolicyPromise(identifier) {
neutron.getVersion().then(setVersion);
return neutron.getQosPolicy(identifier).then(modifyResponse);
function modifyResponse(response) {
response.data.apiVersion = version;
return {data: response.data};
}
}
}
})();
This is my current test file:
(function() {
"use strict";
describe('qosService', function() {
var service;
beforeEach(module('horizon.app.core'));
beforeEach(inject(function($injector) {
service = $injector.get('horizon.app.core.network_qos.service');
}));
describe('getPoliciesPromise', function() {
it("provides a promise that gets translated", inject(function($q, $injector, $timeout) {
var neutron = $injector.get('horizon.app.core.openstack-service-api.neutron');
var session = $injector.get('horizon.app.core.openstack-service-api.userSession');
var deferred = $q.defer();
var deferredSession = $q.defer();
spyOn(neutron, 'getQoSPolicies').and.returnValue(deferred.promise);
spyOn(session, 'get').and.returnValue(deferredSession.promise);
var result = service.getPoliciesPromise({});
deferred.resolve({
data: {
items: [{id: 123, name: 'policy1'}]
}
});
$timeout.flush();
expect(neutron.getQoSPolicies).toHaveBeenCalled();
expect(result.$$state.value.data.items[0].name).toBe('policy1');
}));
});
});
})();
When I run the tests I currently get errors saying:
Expected spy getQoSPolicies to have been called.
As you can see, getQoSPolicies is definitely called. If anyone can see what is wrong with the tests to give me that error it would be much appreciated!! Many thanks in advance!
You should be resolving following promise (deferredSession) along with neutron one, or it won't go inside .then of userSession.get().then(getQoSPolicies):
var deferredSession = $q.defer();
spyOn(session, 'get').and.returnValue(deferredSession.promise);
...
...
deferredSession.resolve({});
deferred.resolve(...);
$timeout.flush();
Resolve that along with the existing and it should work as you expect!

AngularJS how can I call functions properly, outside of the function

I am new to AngularJS and am trying to write a code to extract a data from JSON file.
I wrote a GET function and now want to call the GET function outside of the function.
I have a getData function and on the last line, there is var questions = getData'~~~'. I think this is wrong in my code. How can I call the getData function out side of the DataFactory function.
(function(){
angular
.module("GrammarQuiz")
.factory("DataService", DataFactory);
function DataFactory($log, $http){
var vm = this
var dataObj = {
questions: questions
};
vm.sort = sort;
vm.random = random;
vm.getData = getData;
var temp = 0;
// right now I have questions variable here
// but I want to move this to the outside of the function
//var questions = getData('data1.json');
function getData(apicall){
$log.log('begin!!!');
$http.get('api/' + apicall,
{headers:
{token: 'check!'}
}
).then(function(response){
$log.log(response.data);
questions = response.data;
}, function(response){
$log.log(response.data || "Request failed");
});
}
function sort(array) {
return array.sort(function() {
return .5 - Math.random();
});
}
function random() {
for (var key in dataObj.questions) {
dataObj.questions[key].Choices = sort(dataObj.questions[key].Choices);
}
}
random();
return dataObj;
}
var questions = DataFactory.getData('data1.json');
})();
As I mentioned in my comment, you need to inject your service into your controller. Something like this works:
(function(){
var myApp = angular.module('myApp',[]);
angular
.module("myApp")
.controller("MyCtrl", MyCtrl);
MyCtrl.$inject = ["myApp.myService"]; //injects your service into your controller
function MyCtrl(dataservice) {
var vm = this;
vm.name = 'Superhero';
//calls the service
dataservice.getData();
}
angular.module("myApp").factory("myApp.myService", function() {
//exposes the service's methods
//you need this, vs the vm syntax in your service
var service = {
getData: getData
};
return service;
function getData(){
alert("S");
}
});
})();
JSFiddle: http://jsfiddle.net/Lvc0u55v/8234/
You need to make your api calls in a 'Factory' or 'Services' file. Then make a call to the
'get' method in the Factory file in the 'Controller' file. Code separation is necessary, so
take advantage of the Factories and Controllers.
Refer to example below :
# user.factory.js
# 'app.foo.user' refers to your directory structure i.e. app/foo/user/user.factory.js
(function() {
'use strict';
angular
.module('app.foo.user', [])
.factory('userSvc', UserService);
/* #ngInject */
function UserService(
$log,
$q,
$http,
$window,
$state,
logger,
session,
utils,
API_CONFIG) {
var ENDPOINTS = {
USERS: '/v1/users'
};
/**
* #ngdoc service
* #name app.foo.user
* #description User management
*/
var service = {
get: get
};
/**
* #ngdoc method
* #name get
* #description Returns all users
* #methodOf app.foo.user
* #returms {promise} user or null if not found
*/
function get() {
var q = $q.defer();
var request = utils.buildAuthRequest( session, 'GET', ENDPOINTS.USERS );
$http(request)
.success(function (users) {
q.resolve(users.result);
})
.error(function (error) {
logger.error('UserService.get > Error ', error);
return q.promise;
}
}
})();
//------------------------------------------------------------------------------------
# user.module.js
# 'app.foo.user' refers to your directory structure i.e. app/foo/user/user.module.js
(function() {
'use strict';
angular
.module('app.foo.user', [
]);
})();
//------------------------------------------------------------------------------------
# user-list.controller.js
# This is where you make a call to the 'get' method in the 'user.factory.js'.
# And you gave to inject 'userSvc' in this file so as to connect to the 'user.factory.js' file.
# 'app.foo.admin' refers to your directory structure i.e. app/foo/admin/user-list.controller.js
(function() {
'use strict';
angular
.module('app.foo.admin')
.controller('UsersListController', UsersListController);
/* #ngInject */
function UsersListController(
$scope,
$state,
$timeout,
$log,
userSvc) {
var vm = this;
vm.loading = false;
vm.userSvc = userSvc;
activate();
function activate() {
// init users
vm.userSvc.get().then(
function(users) {
initSearchString(users);
vm.users = users;
},
function(error) {
$log.error(error);
}
);
}
}
})();

angularjs multiple instances of the same service

Say I want more than one instance of a user service (selectedUser & currentUser), they provide the same functionality. Currently I just get around the problem by creating two services that pull their definition from a local function:
angular.module('myUserModule', [])
.factory('userFactory', ['$http', '$q', function ($http, $q) {
return function getUser(userId) {
return new $q(function (resolve) {
//Gets the user with the provided userId
}
}
}])
.value('currentUserId', '')
.value('selectedUserId', '')
.service('currentUserService', ['userFactory', 'currentUserId', User])
.service('selectedUserService', ['userFactory', 'selectedUserId', User]);
function User(userFactory, userId) {
var self = this;
var promise = userFactory(userId);
promise.then(function setUserSuccess(result) {
self.user = result;
}
}
Just wondering how other people have approached this problem.
Updated:
Just to clarify that I am making use of these services as singletons too.
In an unrelated question the solution ended up using a factory that creates multiple instances:
what is the scope of a service in angularjs?
Use the Factory Pattern.
In essence, if you have a User service, you can use a factory to return a unique instance of the service.
function User(userId, $http) {
var self = this;
$http.get('/api/user/', {userId: userId}).success(function(result) {
self.details = result.data;
}):
}
userFactory.$inject = ['$http'];
function userFactory($http) {
return function(user) {
return new User(user, $http);
}
}
angular.module('app').factory('user', userFactory);
But for this example, you can just use $resource. This is a built in angular factory that returns unique Resource instances.
You can use use your factory as an API to return a collection of objects with getter and setter methods.
You can declare new Factory by using the new keywords. However, it is preferable to encapsulate the creation of an instance into a getter method.
Service
(function(){
function userFactory($http, $q){
//Create our userFactory
function userFactory(num){
this.num = num;
};
//Retrieve some user data for example
function getUser(id){
return new $q(function(resolve){
resolve({id: id, data: [1,2,3,4]});
});
}
//Another method
function awesome(){
console.log('Awesome method called by : ' + this.num);
}
//Set method by prototyping
userFactory.prototype.getUser = getUser;
userFactory.prototype.awesome = awesome;
return {
//Use get method to return a new instance of our factory
get: function(){
//Pass a random id for our factory
return new userFactory(Math.ceil(Math.random() * 100));
}
}
}
angular
.module('app')
.factory('UserFactory', userFactory);
})();
The you can create your own instance by using the .get() method :
Controller
(function(){
function Controller($scope, UserFactory) {
var factoryA = UserFactory.get()
var factoryB = UserFactory.get();
console.log('A num instance ' + factoryA.num);
console.log('B num instance ' + factoryB.num);
factoryA.getUser(1).then(function(data){
console.log(data);
});
factoryB.awesome();
console.log('Equality = ' + angular.equals(factoryA, factoryB));
}
angular
.module('app', [])
.controller('ctrl', Controller);
})();
(function(){
function Controller2($scope, UserFactory) {
var factoryC = UserFactory.get()
console.log('C num instance ' + factoryC.num);
}
angular
.module('app')
.controller('ctrl2', Controller2);
})();

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).

Make a factory not Singleton in AngularJS

UPDATE:
Thanks for your reply!
I've rewritten my code:
(function () {
'use strict';
angular
.module('Services', []).factory('services', ['$http', function($http,services) {
function services($http) {
var serviceProvider = function () {
this.data = [];
this.errors = [];
}
var model = {
getInstance:function(){ return new serviceProvider(); }
}
serviceProvider.prototype.init = function(){//put some init stuff here
}
serviceProvider.prototype.getFromRESTServer = function(msg,callback){
return $http.jsonp("http://xxxxxxx/JSONEngine.php?callback=JSON_CALLBACK&action="+callback+"&"+msg);
}
return model;
}
}])
})();
And my controller is defined as:
var uniqueModelInstance = services.getInstance();
uniqueModelInstance.init();
uniqueModelInstance.getFromRESTServer("username="+$scope.username+"&password="+$scope.password,"register").success(function (data) {...}
Are they correct? Now I obtain "Cannot read property 'getInstance' of undefined".
Any suggestion?
Thanks in advance.
Giuseppe
I have an angular factory defined in this way:
services.factory('services', ['$http', function($http) {
var service = {};
return {
getFromRESTServer: function (msg,callback){
return $http.jsonp("http://myUrl/JSONEngine.php?callback=JSON_CALLBACK&action="+callback+"&"+msg);
}
}
}]);
and a controller with doLogin function:
home.controller('registrazioneTraduttoreCtrl', ['$scope', '$rootScope', '$window', 'services', '$location', 'customFactory',
function ($scope, $rootScope, $window, services, $location, customFactory) {
$scope.doLogin= function(username, password) {
services.getFromRESTServer("username="+username+"&password="+password,"login").
success(function (data) {
if(data.jsonError != null || data.errCode != null)
{
alert (data.errMsg);
}
else {
// DO STUFF...
}).error(function(data, status) {
console.error('Repos error', status, data);
})
.finally(function() {
console.log("finally finished repos");
});
}
}]);
The getFromRESTServer can be also executed by another function in another controller (there are 2 different Registration form in my html page and then they call doLogin function).
When I debug my application, the debugger skip from:
services.getFromRESTServer("username="+username+"&password="+password,"login") line (in doLogin function) to the end of getFromRESTServer funcion without going in and then re-execute the doLogin function with username and password NULL and now it enter in the core of getFromRESTServer function.
Any ideas?
Thanks in advance.
Giuseppe
You can do this by returning a new instance of any factory which is called. Look at this Plunker or try the following codes:
/**
* Non singleton factory example
*
* #name non-singleton-example
* #author Nils Gajsek <info#linslin.org>
*/
(function () {
//use strict -> ECMAScript5 error reporting
'use strict';
// ################################################ angularJS Module define // ####################################
/**
* DB service, part of app module
*/
angular
.module('app.model', []) // [-_-]
.factory('model', ['$http', model]);
/**
* Model factory wrapper
*
* #param {object} $http
*
* #returns self
*/
function model($http) {
// ################################################## Vars // ##############################################
var serviceProvider = function(){
/**
* Data store
* #type {Array}
*/
this.data = [];
/**
* Error store
* #type {Array}
*/
this.errors = [];
}
// ############################################### Functions // ############################################
/**
* Model instance provider handler
* This object is returned on the end of this object
*/
var model = {
getInstance:function(){ return new serviceProvider(); }
}
/**
* Model init function, provides
*/
serviceProvider.prototype.init = function(){
//put some init stuff here
}
/**
* Example function
*
* #returns {{this}}
*/
serviceProvider.prototype.someFunction = function(){
//do some stuff with model
}
//return model -> non-singleton model instance object
return model;
}
})();
This is how you receive it as unique instance.
var uniqueModelInstance = model.getInstance();
uniqueModelInstance.init();
Or better (but you need to return the instance itself by calling init() the function)
var uniqueModelInstance = model.getInstance().init();

Categories