Return a promise in a factory - javascript

I have a factory where I wanna return a promise inside a function but my controller says everytime something like:
Provider 'personFactory' must return a value from $get factory method.
My factory:
(function () {
'use strict';
angular
.module('testApp')
.factory('personFactory', personFactory);
personFactory.$inject = ['storage'];
function personFactory(storage) {
storage.getData().then(function (response) {
return {
allData: function () {
return response
}
}
});
}
})();
My controller:
(function () {
'use strict';
angular
.module('testApp')
.controller('menuListController', menuListController);
menuListController.$inject = ['$scope', 'personFactory', '$rootScope'];
function menuListController($scope, personFactory) {
$scope.fromFactory = personFactory.allData();
console.log($scope.fromFactory)
}
})();
I think I have to double return both the functions, but can't get the syntax right to call it also in the controller if that is the problem.

Your factory definition has to return something, that's the problem:
function personFactory(storage) {
return storage.getData().then(function (response) {
return {
allData: function () {
return response
}
}
});
}
Now, I don't know if that's what you really need. I usually return a function or object in the factory that then I use.
With your approach you will invoke getData only once and receive the promise directly in your controller.
I like it, now that I think about it :)
But! Looking at the rest of your code, I think you're not expecting to get the promise after all.
You would have to do something like this anyway:
personFactory.then(function () {
$scope.fromFactory = data.allData();
});
Which again makes me think you don't need to do that allData business. Here's what I'd do:
function personFactory(storage) {
return storage.getData();
}
personFactory.then(function (response) {
$scope.fromFactory = response;
});
How does that look?

Related

Angular Service working synchronously with Controller

I'm fairly new to AngularJS and have just begun to grasp many of the concepts I especially like the MVC design pattern. But I am having a difficult time implementing the Service layer in my application.
What I am finding is that after my Controller calls a method within the service, it continues with code execution before the service returns the data; so that by the time the service does return the data, it isn't of any use to the controller.
To give a better example of what I'm saying, here is the code:
var InsightApp = angular.module('InsightApp', ['chart.js']);
// Module declaration with factory containing the service
InsightApp.factory("DataService", function ($http) {
return {
GetChartSelections: function () {
return $http.get('Home/GetSalesData')
.then(function (result) {
return result.data;
});
}
};
});
InsightApp.controller("ChartSelectionController", GetAvailableCharts);
// 2nd Controller calls the Service
InsightApp.controller("DataController", function($scope, $http, DataService){
var response = DataService.GetChartSelections();
// This method is executed before the service returns the data
function workWithData(response){
// Do Something with Data
}
}
All the examples I've found seem to be constructed as I have here or some slight variation; so I am not certain what I should be doing to ensure asynchronous execution.
In the Javascript world, I'd move the service to the inside of the Controller to make certain it executes async; but I don't how to make that happen in Angular. Also, it would be counter intuitive against the angular injection to do that anyway.
So what is the proper way to do this?
http return a promise not the data, so in your factory your returning the $http promise and can use it like a promise with then, catch, finally method.
see: http://blog.ninja-squad.com/2015/05/28/angularjs-promises/
InsightApp.controller("DataController", function($scope, $http, DataService){
var response = DataService.GetChartSelections()
.then(function(res) {
// execute when you have the data
})
.catch(function(err) {
// execute if you have an error in your http call
});
EDIT pass params to model service:
InsightApp.factory("DataService", function ($http) {
return {
GetChartSelections: function (yourParameter) {
console.log(yourParameter);
return $http.get('Home/GetSalesData')
.then(function (result) {
return result.data;
});
}
};
});
and then :
InsightApp.controller("DataController", function($scope, $http, DataService){
var response = DataService.GetChartSelections('only pie one')
.then(function(res) {
// execute when you have the data
})
.catch(function(err) {
// execute if you have an error in your http call
});
You should proceed like this :
DataService.GetChartSelections().then(function (data) {
workWithData(data);
}
Actually $http.get returns a Promise. You can call the method then to handle the success or failure of your Promise
Should it not be like this, when your $http returns a promise or you pass a callback.
With passing callback as a param.
InsightApp.factory("DataService", function ($http) {
return {
GetChartSelections: function (workWithData) {
return $http.get('Home/GetSalesData')
.then(function (result) {
workWithData(result.data);
});
}
};
});
Controller code:
InsightApp.controller("DataController", function($scope, $http, DataService){
var response = DataService.GetChartSelections(workWithData);
// This method is executed before the service returns the data
function workWithData(response){
// Do Something with Data
}
}
Or use then or success:
var response = DataService.GetChartSelections().then(function(res){
//you have your response here... which you can pass to workWithData
});
Return the promise to the controller, dont resolve it in the factory
var InsightApp = angular.module('InsightApp', ['chart.js']);
// Module declaration with factory containing the service
InsightApp.factory("DataService", function ($http) {
return {
GetChartSelections: function () {
return $http.get('Home/GetSalesData');
}
};
});
In the controller,
var successCallBk =function (response){
// Do Something with Data
};
var errorCallBK =function (response){
// Error Module
};
var response = DataService.GetChartSelections().then(successCallBk,errorCallBK);

How to conditionally flush a $timeout in Angular unit test

I have code that is something like this:
function doThing() {
if (invalidInput) {
console.error('Invalid input.');
return;
}
$timeout(function() {
MyService.doThing();
}, 1000);
}
I want to test that MyService.doThing isn't called when invalid input is passed in.
If I call doThing(invalidInput) without doing $timeout.flush(), MyService.doThing wouldn't be called, regardless of whether I have lines 2-5. So to really test whether MyService.doThing is called when invalid input is passed in, I need to call $timeout.flush.
The problem is that it throws an error if I try to flush when there's nothing to flush. Error: No deferred tasks to be flushed.
How can I handle this scenario? I'd like to do something like, $timeout.flushIfFlushable().
I suggest define two separate unit tests to verify the behavior of your doThing function. Try following code:
Controller
(function () {
'use strict';
angular.module('myApp', [])
.controller('MainCtrl', function ($timeout, MyService) {
var vm = this;
vm.invalidInput = true;
vm.doThing = doThing;
function doThing() {
if (vm.invalidInput) {
return;
}
$timeout(function () {
MyService.doThing();
}, 1000);
}
});
})();
Service
(function () {
'use strict';
angular.module('myApp').service('MyService', MyService);
function MyService() {
this.doThing = function () {
// doThing code
};
}
})();
Unit test
'use strict';
describe('Controller: MainCtrl', function () {
beforeEach(module('myApp'));
var vm,
$timeout,
MyService;
beforeEach(inject(function (_$controller_, _$timeout_, _MyService_) {
$timeout = _$timeout_;
MyService = _MyService_;
vm = _$controller_('MainCtrl', {
$timeout: $timeout,
MyService: MyService
});
}));
it('should call doThing for valid inputs', function () {
spyOn(MyService, 'doThing').andCallThrough();
vm.invalidInput = false;
vm.doThing();
$timeout.flush();
expect(MyService.doThing).toHaveBeenCalled();
});
it('should not call doThing for invalid inputs', function () {
spyOn(MyService, 'doThing').andCallThrough();
vm.invalidInput = true;
vm.doThing();
expect(MyService.doThing).not.toHaveBeenCalled();
});
});
With the first test we expect to call MyService.doThing() function.
On other hand, if you have invalidInput as true, the previous function should not be called.
I hope It helps.
No uncertainty is welcome in unit tests, it should be predictable whether there is something to flush for $timeout or not.
In this piece of code two cases should be tested, in both $timeout shouldn't be a blackbox, it should be a stub instead (optionally a spy that wraps around the real service).
beforeEach(module('app', ($provide) => {
$provide.decorator('$timeout', ($delegate) => {
var timeoutSpy = jasmine.createSpy().and.returnValue($delegate);
// methods aren't copied automatically to spy
return angular.extend(timeoutSpy, $delegate);
});
}));
The first is falsey invalidInput:
...
MyService.doThing();
expect($timeout).not.toHaveBeenCalled();
And the second one is truthy invalidInput:
...
MyService.doThing();
expect($timeout).toHaveBeenCalledWith(jasmine.any(Function), 1000);
$timeout.flush();
expect(MyService.doThing).toHaveBeenCalledTimes(2);
Disregarding this case, it is generally a good thing to return promises from promise-powered functions:
function doThing() {
if (invalidInput) {
console.error('Invalid input.');
return;
}
return $timeout(function() {
return MyService.doThing();
}, 1000);
}
This way the callers (most likely specs) may have some control on function's asynchronous behaviour.
Answering the question directly, the expected way to do 'flushIfFlushable()' is
try {
$timeout.verifyNoPendingTasks(); // just for semantics, not really necessary
$timeout.flush();
} catch (e) {}
Which should be avoided for the reasons listed above.

proper way to call http (RESTFUL WebAPI) in angularjs

I have following controller code
module.registerController('DemoCtrl', function ($scope, myFactory) {
myFactory.get(function (data) {
console.log(data); /// data is always undefined
});
});
and following the factory which is calling restful webapi
module.registerFactory('myFactory', ['$http',
function ($http) {
function get(callback) {
$http.get('mywebapiurl')
.success(function (response) {
//debugger; data comes back from server
callback(response);
}).error(function (response, status, headers, config) {
callback(response);
});
}
return {
get: get
}
}]);
The factory is calling webapi service and does gets the data back. However in controller the data doesnt get returned.
Am I missing something obvious here? Also not sure if this is the best way to call webservice in angularjs in controller using factory. Any inputs are most welcome.
Thanks,
You want to return a promise instead of passing a callback. As $http.get already returns a promise, you can just return that, or a derived promise that returns the response data directly. By the way, your factory looks like it should be a service instead:
angular.moudule('yourApp')
.service('myService', ['$http', myService]);
function myService($http) {
this.get = function(url) {
return $http.get(url)
.then(function transformData(response){
return response.data;
}).catch(function onError(rejectionResponse){
//Whatever you want to do here
});
}
}
This way myService.get will return a promise you can .then(), .catch() and .finally() on what you want, staying in the frameworks coding style. For example:
var squirrels = [];
myService.get('http://squirrelshop.com/api/squirrels')
.then(function(squirrelsData){
console.log('Got the squirrels!');
squirrels = squirrelsData;
}).catch(function(rejection){
console.warn('Couldnt fetch squirrels. Reason: ' + rejection);
});
controller code
module.registerController('DemoCtrl', function ($scope, myFactory) {
myFactory.get("url").then(function(d) {
console.log(d.data);
}
});
});
factory which is calling restful webapi
module.registerFactory('myFactory', ['$http',
function ($http) {
var apiFactory = {
get:function(url){
return $http.get(url);
}
}
return apiFactory;
}]);
Success and failure in factory
module.registerFactory('myFactory', ['$http',
function ($http) {
var apiFactory = {
get:function(url){
return $http.get(url).then(function(response){
// success
return responce.data;
},function(error){
//failure
return error;
};
}
}
return apiFactory;
}]);

Using promises with $routeProvider to create resolve variables

I have 3 factory functions that I would like to chain together to be used in a resolve stanza of a route:
1st function is a simple REST call to $http:
app.factory('services', ['$http', '$q', function ($http, $q) {
var serviceBase = 'services/';
var obj = {};
obj.getSelect = function (db, table, columns, keys) {
return $http.post(serviceBase + 'getSelect', {
selectDB: db,
selectTable: table,
selectColumn: columns,
selectKeys: keys
}).then(function (results) {
return results;
});
};
// more objects follow
}
It is used by the next function that simply calls services.getSelect to retrieve some records:
app.factory('myFunctions', ['services', '$q', function (services, $q) {
return {
fGetData: function () {
services.getSelect(
'myDB', // DB
'tableInDB', // Table
"*", // Columns
"" // Keys
).then(
function (retObj) {
return $q.all (retObj);
console.log('myFunctions.fGetData', retObj);
}
)
}
}
}]);
The last function calls myFunctions.fGetData. Its purpose is to return values to the resolve stanza:
app.factory("getInitData",['myFunctions','$q', function (myFunctions, $q) {
return function () {
var initData = myFunctions.fGetData();
return $q.all( {initData: results} ).then(function (results) {
return {
initDataReturn: results
};
console.log('getInitData', results);
});
}
}]);
and finally the resolve stanza:
app.config( ['$routeProvider', 'myConst', function ($routeProvider) {
$routeProvider.when(myConst.adminButtonURL, {
templateUrl: '/myTemplateURL',
controller: myControler,
resolve: {
initDataObj: function(getInitData){
return getInitData();
}
}
}
}]);
In the controller the initDataObj is returned:
app.controller('myController', function ($scope, nitDataObj {
$scope.surveyGroup = initDataObj.initData;
});
The console logs always show that 'getInitdata' always fires first and the return is a null object.
The function myFunctions.fGetData always fires first and the correct data is returned.
To miss-quote a song from He Haw: "I've searched the world over and I thought I'd find the answer (true love is lyric)", but while there have been very interesting bits of clues including
http://busypeoples.github.io/post/promises-in-angular-js/ &
http://www.dwmkerr.com/promises-in-angularjs-the-definitive-guide/
nothing has had the complete answer.
Thanks to all.
ok i think in part this has to do with the way you are using $q
$q.all take an array or an object containing promises
https://docs.angularjs.org/api/ng/service/$q
in your services factory you are resolving the promise and then returning the results
and then in myFunctions you are taking the returned value and trying to give it to $q.all which doesnt accept what you're giving it
looks like your wanting to keep sending a promise back from each factory you could do something like
app.factory('services', ['$http', function ($http) {
var serviceBase = 'services/';
var obj = {};
obj.getSelect = function (db, table, columns, keys) {
return $http.post(serviceBase + 'getSelect', {
selectDB: db,
selectTable: table,
selectColumn: columns,
selectKeys: keys
});
};
// more objects follow
}
app.factory("myFunctions", ["$q", "services", function($q, services){
return {
fGetData: function(){
var deferred = $q.defer();
services.getSelect()
.success(function(results){
// do something with the data
deferred.resolve(results);
});
return deferred.promise;
}
};
}]);
app.factory("getInitData",['myFunctions', function (myFunctions) {
return function () {
myFunctions.fGetData()
.then(function(data){
// do something with the data
});
}
}]);

AngularJS : testing a factory that returns a promise, while mocking a service that uses $http

I've got a service that has the following method (among others), which returns an $http promise
function sessionService($http, serviceRoot) {
return {
getAvailableDates: function () {
return $http.get(serviceRoot + '/session/available_dates');
}
};
};
angular.module('app').service('sessionService', ['$http', 'serviceRoot', sessionService]);
And then another factory that kinda wraps it and caches/adds data to localStorage. This returns a regular promise
angular.module('app')
.factory('AvailableDates', AvailableDates);
AvailableDates.$inject = ['sessionService', '$window', '$q'];
function AvailableDates(sessionService, $window, $q) {
var availableDates = [];
return {
getAvailableDates: getAvailableDates
};
function getAvailableDates() {
var deferred = $q.defer();
var fromStorage = JSON.parse($window.sessionStorage.getItem('validDates'));
if (availableDates.length > 0) {
deferred.resolve(availableDates);
} else if (fromStorage !== null) {
deferred.resolve(fromStorage);
} else {
sessionService.getAvailableDates()
.success(function (result) {
availableDates = result;
$window.sessionStorage.setItem('validDates', JSON.stringify(availableDates));
deferred.resolve(availableDates);
});
}
return deferred.promise;
}
}
This all works fine. My problem is I can't figure out how to test this thing while mocking the sessionService. I've read all the related stackoverflow questions, and tried all kinds of different things, to no avail.
Here's what my test currently looks like:
describe('testing AvailableDates factory', function () {
var mock, service, rootScope, spy, window, sessionStorageSpy, $q;
var dates = [ "2014-09-27", "2014-09-20", "2014-09-13", "2014-09-06", "2014-08-30" ];
var result;
beforeEach(module('app'));
beforeEach(function() {
return angular.mock.inject(function (_sessionService_, _AvailableDates_, _$rootScope_, _$window_, _$q_) {
mock = _sessionService_;
service = _AvailableDates_;
rootScope = _$rootScope_;
window = _$window_;
$q = _$q_;
});
});
beforeEach(inject(function () {
// my service under test calls this service method
spy = spyOn(mock, 'getAvailableDates').and.callFake(function () {
return {
success: function () {
return [ "2014-09-27", "2014-09-20", "2014-09-13", "2014-09-06", "2014-08-30" ];
},
error: function() {
return "error";
}
};
});
spyOn(window.sessionStorage, "getItem").and.callThrough();
}));
beforeEach(function() {
service.getAvailableDates().then(function(data) {
result = data;
// use done() here??
});
});
it('first call to fetch available dates hits sessionService and returns dates from the service', function () {
rootScope.$apply(); // ??
console.log(result); // this is printing undefined
expect(spy).toHaveBeenCalled(); // this passes
expect(window.sessionStorage.getItem).toHaveBeenCalled(); // this passes
});
});
I've tried various things but can't figure out how to test the result of the AvailableDates.getAvailableDates() call. When I use done(), I get the error:
Timeout - Async callback was not invoked withing timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL (I've tried overriding this value, no luck).
If I take out the done(), and just call rootScope.$apply() after the .then is called, I get an undefined value as my result.
What am I doing wrong?
I see more issues in your example.
The main problem is the success definition in the mock. Success is a function, which has a function as a parameter - callback. Callback is called when data is received - data is passed as the first argument.
return {
success: function (callback) {
callback(dates);
}
};
Simplified working example is here http://plnkr.co/edit/Tj2TZDWPkzjYhsuSM0u3?p=preview
In this example, mock is passed to the provider with the module function (from ngMock) - you can pass the object with a key (service name) and value (implementation). That implementation will be used for injection.
module({
sessionService:sessionServiceMock
});
I think test logic should be in one function (test), split it into beforeEach and test is not a good solution. Test is my example; it's more readable and has clearly separated parts - arrange, act, assert.
inject(function (AvailableDates) {
AvailableDates.getAvailableDates().then(function(data) {
expect(data).toEqual(dates);
done();
});
rootScope.$apply(); // promises are resolved/dispatched only on next $digest cycle
expect(sessionServiceMock.getAvailableDates).toHaveBeenCalled();
expect(window.sessionStorage.getItem).toHaveBeenCalled();
});

Categories

Resources