I would like to ask is it possible to return recursive factory method. I will show some code so you could understand me more.
We have factory:
angular.module('module')
.factory('synchronizationStatus', ['$http', 'apiBase', '$timeout', function ($http, apiBase, $timeout) {
var service_url = apiBase.url;
function syncCompleted ( correlationId ) {
checkSync (correlationId)
.then(function(response){
$timeout(function() {
// if I get response.data true, i want to proceed to controller
if(response.data){
return "now I want to return result to controller"
}
else {
// check again
checkSync(correlationId)
}
}, 500);
})
}
function checkSync( correlationId ){
return $http.get(service_url + query);
}
return {
syncCompleted: syncCompleted
};
}]);
Main idea with this factory method is that i constantly (every 500ms each) send ajax request to backend and check if some operation is completed or no, and when it is completed i would like to send promise to controller function, which looks like this:
function save( client ) {
clients.addClient( client )
.then( function(response) {
synchronizationStatus.syncCompleted(response.data.CorrelationId);
}, onSaveError)
.then( redirectToList, onSaveError )
.finally( unblock );
}
after backend returns true to my factory method i would like to execute other functions in my controller. Of course I could do recursion in my controller and it would solve this problem. Although I have to reuse this recursion in many other controllers so I would like to reuse this method.
Yes, you should be able to do this, but you need to change the code in your factory a little:
angular.module('module')
.factory('synchronizationStatus', [
'$http',
'apiBase',
'$timeout',
function ($http, apiBase, $timeout) {
var service_url = apiBase.url;
function waitSyncCompletion( correlationId ) {
return checkSync (correlationId)
.then(function(response){
if (response.data) {
return "all done!";
}
return $timeout(function() {
// check again
return waitSyncCompletion(correlationId);
}, 500);
});
}
function checkSync( correlationId ){
var query = '...'; // determine query
return $http.get(service_url + query);
}
return {
waitSyncCompletion: waitSyncCompletion
};
}
]);
Then in your controller, you would need to use a return so that you can wait for the operation to complete:
function save( client ) {
clients.addClient( client )
.then( function(response) {
return synchronizationStatus.waitSyncCompletion(response.data.CorrelationId);
})
.then(function (result) { console.log(result); })
.then( redirectToList )
.catch( onSaveError )
.finally( unblock );
}
Make use of a custom deferred (promise), using Angular's $q. Make sure that you inject the $q dependency in your factory and create the deferred at the start of your factory:
var deferred = $q.defer()
At the point where the promise must resolve, add:
deferred.resolve();
And make sure you return the promise at the bottom of your syncCompleted function:
return deferred.promise;
Additionally, you could add deferred.reject() if you also want error-handling. Furthermore, you could add arguments to reject() and resolve() if you need to.
As an alternative, you could achieve the same goal without using $q. $timeout() also returns a promise, and if you return a promise in the callback given to then(), it will kind of 'replace' the 'parent' promise.
Related
Which is the right way to declare a service ?
.service('myService', function(myFactory) {
myFactory.LoadData("...",function(newData){
this.data= newData;
});
}
or
.service('myService', function() {
var data= {};
myFactory.LoadData("...",function(newData){
data= newData;
return data ;
});
}
I don't want to make http calls everytime I use it but only once, and then access the data whenever I want.
I've tried the first example but i'm getting 'undefined' value when I try to use it in my controller.
EDIT:
ok here is my code:
the service :
.service('ClassService', function(ClassFactory) {
ClassFactory.query(function(rawClasses){
this.classes= [];
rawClasses.forEach(function(classe) {
var classeRow={};
classeRow.grade=classe.grade;
classe.branch.forEach(function(branch) {
classeRow._id=branch._id;
classeRow.branch= branch.name;
classeRow.fees= branch.fees;
branch.sub_class.forEach(function(subClass) {
classeRow.label=subClass.label;
classeRow.name= _getGradeName_(classeRow.grade)+(classeRow.branch || '')+ (subClass.label || '');
console.info('ClasseService hihihi!');
this.classes.push(_byValue_(classeRow));
console.log( this.classes);
}, this);
}, this);
}, this);
});
})
and the controller:
.controller('StudentController', function(StudentFactory, ClassService, $scope, $stateParams) {
//...
$scope.classes= ClassService.classes;
//but here console.log($scope.classes) gives 'undefined'
//...
});
The query in your service is running asynchronously so you need to use a promise to wait for the value to become available.
If you make ClassFactory.query() return a promise instead of using a callback it will all become much simpler. Use thePromise.then() in ClassService to handle the completion, but that also returns a promise which you should expose to the controller. Then in the controller use another .then() to know when the data is available.
So assuming you've updated ClassFactory.query() to return a promise:
.service('ClassService', function(ClassFactory) {
var promise =ClassFactory.query();
this.dataLoaded = promise.then(... the code to convert rawClasses to this.classes goes here...);
})
and:
.controller('StudentController', function(StudentFactory, ClassService, $scope, $stateParams) {
//...
ClassService.dataLoaded.then(function () {
$scope.classes= ClassService.classes;
});
});
I am trying to handle restangular calls entirely from a service to keep the controller light, and also so I can manipulate the data further within the service later.
Struggling with promises, which I think is my issue. If I can avoid it, I dont want the service to return the promise, and then use .then() in the controller.
If I make the restangular call directly from the controller it works fine.
angular.module('vehicle', ['restangular'])
.config(
function(RestangularProvider) {
RestangularProvider.setBaseUrl('https://jsonplaceholder.typicode.com/');
RestangularProvider.setResponseExtractor(function(response, operation, what) {
if (operation === 'getList' && !Array.isArray(response)) {
return [response];
}
return response;
});
})
.controller('VehicleController', function($scope, VehicleService, Restangular) {
$scope.vehicles = VehicleService.getVehicles();
Restangular.all('posts').getList().then(function(vehicle) {
$scope.moreVehicles = vehicle.plain();
});
})
.service('VehicleService', function(Restangular) {
VehicleService = {};
VehicleService.vehicles = [];
VehicleService.getVehicles = function() {
Restangular.all('posts').getList().then(function(vehicle) {
VehicleService.vehicles = vehicle.plain();
return VehicleService.vehicles;
});
}
return VehicleService;
});
https://plnkr.co/edit/q1cNw6gN12pKsiPaD1o0?p=preview
Any ideas why my combination of service and promise wont return the data to scope?
I'm trying to do an ajax call via $http service inside a custom service. Then I want to customize, inside my controller, the data received in the custom service.
I wrap the customizing data function within $interval inside the controller: by this way I can customize my data when it is received.
The problem is: while the data is correctly logged in the service, the service seems like it doesn't return anything, although it should have returned (return response.data.Items)
So $interval loops indefinitely, and I can't customize my data.
var myApp = angular.module('MyApp', []);
myApp.service('ajaxcall', ['$http', function($http) {
this.getjson = function () {
$http.get("http://localhost:3000/one")
.then(function(response) {
console.log(response.data.Items); //data logged correctly
return response.data.Items;
});
}
}])
.controller('MyCtrl', ['$scope', '$interval', 'ajaxcall', function($scope, $interval, ajaxcall) {
var new_items = ajaxcall.getjson();
customize_data = $interval(function () { //loops indefintely, new_items=undefined, why?
if (new_items) {
// customize data
}
}, 200);
for(var i=0; i<new_items.length; i++) {
$scope.items.push(new_items[i]);
}
}]);
You could say: just move the customize data function in the custom service. First at all I don't want to do it. Secondly it doesn't even make sense: the $scope is not available in a service, so in any case I should wait for the $http reply.
There were several things which I wanted to point out there.
Do return $http promise from this.getjson method, so that you can chain that promise while getting data from it.
this.getjson = function() {
//returned promise here
return $http.get("http://localhost:3000/one")
.then(function(response) {
console.log(response.data.Items); //data logged correctly
return response.data.Items;
});
}
var new_items = ajaxcall.getjson() line doesn't stored the data returned by getjson call, it will have undefined value as your currently getting. After finish up above change, new_items will hold promise return by ajaxcall.getjson. Thereafter use $q.when to keep eye on promise to get resolved & check for data inside its .then function.
customize_data = $interval(function() { //loops indefintely, new_items=undefined, why?
$q.when(new_items).then(function(res) {
if (customizeData) {
//Do Logic
}
})
}, 200);
Side Note: You could face problem with this code, as you had 200ms time for each interval . Which can make multiple ajax calls before completing the last call(which would be kind of unexpected behaviour). To
resolve such issue you could use $interval.cancel(customize_data);
//once desired interval work has done
if you want to get the return data, you would write a factory instead of service!
code:
myApp.factory('ajaxcall', ['$http', function($http) {
return {
getjson : function () {
$http.get("http://localhost:3000/one")
.then(function(response) {
console.log(response.data.Items); //data logged correctly
return response.data.Items;
});
}
}
}])
You are misusing the promise returned by your asynchronous call. Here is what you need to do in your controller to change the data:
ajaxcall.getjson()
.then(function(new_items) {
// customize data
// this part should go here as well
for(var i=0; i<new_items.length; i++) {
$scope.items.push(new_items[i]);
}
});
No need to use intervals or timeouts. Pay attention, ajaxcall.getjson() does NOT return your data, it returns the promise resolved with your items.
Read about the promises in angular.
use promise when your making http calls from service
myApp.service('ajaxcall', ['$http', '$q', function($http, $q) {
this.getjson = function() {
var q = $q.defer();
$http.get("http://localhost:3000/one")
.success(function(data) {
console.log(data); //data logged correctly
q.resolve(data);
})
.error(function(err) {
q.reject(err);
});
return q.promise;
}
}]);
changes in controller to wait for promise
ajaxcall.getjson()
.then(function(data){
console.log(data);
});
Is there a way to return an HttpPromise (or something similar) to mimic a call to $http? I want to set a global variable that indicates whether the real HTTP request is made or whether a fake HttpPromise object is returned with fake data.
For example, I have a service that is similar to this:
angular
.module('myservice')
.factory('MyService', ['$http', function($http) {
return {
get : function(itemId) {
if (isInTestingMode) {
// return a promise obj that returns success and fake data
}
return $http.get("/myapp/items/" + itemId);
}
};
} ]);
And in my controller, I have a call to the aforementioned service that looks similar to this:
// Somewhere in my controller
MyService.get($scope.itemId)
.success(function(data) {
$scope.item = data;
})
.error(function(data, status, headers, config) {
$scope.notFound = true;
});
I'm trying to not change the controller code; I want the success and error chaining to still work when in my "isInTestMode".
Is it possible to fake an HttpPromise in the way that I described in the service?
Below is a revised edition of the "MyService" above (a snippet) containing a success and error on the promise object. But, how do I execute the success method?
return {
get : function(itemId) {
if (isInTestingMode) {
var promise = $.defer().promise;
// Mimicking $http.get's success
promise.success = function(fn) {
promise.then(function() {
fn({ itemId : "123", name : "ItemName"}, 200, {}, {});
});
return promise;
};
// Mimicking $http.get's error
promise.error = function(fn) {
promise.then(null, function(response) {
fn("Error", 404, {}, {});
});
return promise;
};
return promise;
}
return $http.get("/myapp/items/" + itemId);
}
}
Just use the deferred method of the $qservice
var fakeHttpCall = function(isSuccessful) {
var deferred = $q.defer()
if (isSuccessful === true) {
deferred.resolve("Successfully resolved the fake $http call")
}
else {
deferred.reject("Oh no! Something went terribly wrong in your fake $http call")
}
return deferred.promise
}
And then you can call your function like an $http promise (you have to customize whatever you want to put inside of it, of course).
fakeHttpCall(true).then(
function (data) {
// success callback
console.log(data)
},
function (err) {
// error callback
console.log(err)
})
I found that this post is similar to what I was asking.
However, I wanted a way to mock my service call so that fake data could be returned instead of issuing a true HTTP request call. The best way to handle this situation, for me, is to use angular's $httpBackend service. For example, to bypass a GET request to my "items" resource BUT to not bypass GETs of my partials/templates I would do something like this:
angular
.module('myApp', ['ngMockE2E'])
.run(['$httpBackend', function($httpBackend) {
$httpBackend
.whenGET(/^partials\/.+/)
.passThrough();
$httpBackend
.whenGET(/^\/myapp\/items\/.+/)
.respond({itemId : "123", name : "ItemName"});
}]);
See this documentation for more information on $httpBackend.
I finally found a way using jasmin. $httpBackend was no option for me, as there were also non-$http-methods I needed mock on the same service. I also think that the controller test needing to specify the url is not perfect as imho the controller and its test should not need to know about it.
Here is how it works:
beforeEach(inject(function ($controller, $rootScope, $q) {
scope = $rootScope.$new();
mockSvc = {
someFn: function () {
},
someHttpFn: function () {
}
};
// use jasmin to fake $http promise response
spyOn(mockSvc, 'someHttpFn').and.callFake(function () {
return {
success: function (callback) {
callback({
// some fake response
});
},
then: function(callback) {
callback({
// some fake response, you probably would want that to be
// the same as for success
});
},
error: function(callback){
callback({
// some fake response
});
}
}
});
MyCtrl = $controller('MyCtrl', {
$scope: scope,
MyActualSvc: mockSvc
});
}));
You can implement your FakeHttp class:
var FakeHttp = function (promise) {
this.promise = promise;
this.onSuccess = function(){};
this.onError = function(){};
this.premise.then(this.onSuccess, this.onError);
};
FakeHttp.prototype.success = function (callback) {
this.onSuccess = callback;
/**You need this to avoid calling previous tasks**/
this.promise.$$state.pending = null;
this.promise.then(this.onSucess, this.onError);
return this;
};
FakeHttp.prototype.error = function (callback) {
this.onError = callback;
/**You need this to avoid calling previous tasks**/
this.promise.$$state.pending = null;
this.promise.then(this.onSuccess, this.onError);
return this;
};
Then in your code, you would return a new fakeHttp out of the promise.
if(testingMode){
return new FakeHttp(promise);
};
The promise must be asynchronous, otherwise it won't work. For that you can use $timeout.
easy peasy!
You can do it using angular-mocks-async like so:
var app = ng.module( 'mockApp', [
'ngMockE2E',
'ngMockE2EAsync'
]);
app.run( [ '$httpBackend', '$q', function( $httpBackend, $q ) {
$httpBackend.whenAsync(
'GET',
new RegExp( 'http://api.example.com/user/.+$' )
).respond( function( method, url, data, config ) {
var re = /.*\/user\/(\w+)/;
var userId = parseInt(url.replace(re, '$1'), 10);
var response = $q.defer();
setTimeout( function() {
var data = {
userId: userId
};
response.resolve( [ 200, "mock response", data ] );
}, 1000 );
return response.promise;
});
}]);
i have problems when it comes to $http promises in angularjs. i am doing this in my service: (the getSomething function should chain two promises)
the second function uses a external callback function!
app.service('blubb', function($http, $q) {
var self = this;
this.getSomething = function(uri, data) {
return self.getData(uri).then(function(data2) {
return self.compactData(uri, data2);
});
};
this.getData = function(uri) {
var deferred = $q.defer();
$http.get(uri).success(function(data) {
deferred.resolve(data);
}).error(function() {
deferred.reject();
});
return deferred.promise;
};
this.compactData = function(uri, data) {
var deferred = $q.defer();
/* callback function */
if(!err) {
console.log(compacted);
deferred.resolve(compacted);
} else {
console.log(err);
deferred.reject(err);
}
/* end of function */
return deferred.promise;
};
});
when i use the service in my controller it doesn't output the console.log:
blubb.getSomething(uri, input).then(function(data) {
console.log(data)
});
edit:
if i define the callback function by myself in 'compactData' it works, but i am using "jsonld.compact" from https://raw.github.com/digitalbazaar/jsonld.js/master/js/jsonld.js and THIS doesn't work!
jsonld.compact(input, context, function(err, compacted) {
if(!err) {
console.log(compacted);
deferred.resolve(compacted);
} else {
deferred.reject('JSON-LD compacting');
}
});
i am getting the console.log output in jsonld.compact but the resolve doesn't work and i don't know why..
it only works with $rootScope.$apply(deferred.resolve(compacted));
I'm using chaining promises like this:
$http.get('urlToGo')
.then(function(result1) {
console.log(result1.data);
return $http.get('urlToGo');
}).then(function(result2) {
console.log(result2.data);
return $http.get('urlToGo');
}).then(function(result3) {
console.log(result3.data);
});
Chaining promises works here : jsfiddle
In your implementation, if $http.get or compactData goes wrong your console.log(data) will not be call.
You should maybe catch errors :
blubb.getSomething(uri, input).then(function(data) {
console.log(data);
}, function(err) {
console.log("err: " + err);
});
Whenever you use an external (external to AngularJS) callback that runs in a new turn/tick, you have to call $apply() on the appropriate scope after it has been invoked. This lets AngularJS know it has to update. You'll probably want to make sure you're only calling it once -- after all of the promises have been resolved. As an aside, jsonld.js provides a promises/future API, so if you're already using promises, you don't have to do that wrapper code above. Instead you can do:
var promisesApi = jsonld.promises();
var promise = promisesApi.compact(input, context);
// do something with the promise
I would suggest you to use a Factory instead of a service.
Just return the function from the factory and use it in your controller