I'm testing a service that uses another service for API calls, let's call this the data service. The data service is tested elsewhere, so I've abstracted it away with a simple implementation that contains empty functions; I'm returning data via a deferred object and Jasmine's spyOn syntax.
The trouble I'm finding with this approach is when the data is returned, it's not immediately available on the calling object, as it would be if I used $httpBackend. Aware I could just use $httpBackend, but I'd like to know if I've missed something (simple or otherwise) in this approach.
Example section of code I'm trying to test:
storeTheData = dataService.getSomeData();
storeTheData.$promise.then(function(data) {
/*this would work*/
console.log(data);
/*but this would not, when testing using $q*/
_.forEach(storeTheData, function(storedData) {
/*do something with each object returned*/
});
});
As a side note, I don't think the situation is helped by the ...$promise.then on another line, but ideally I wouldn't change the code (I'm providing test coverage to something written a while ago...)
Example of the test:
beforeEach(
...
dataService = {
getSomeData: function () { }
};
getSomeDataDeferred = $q.defer();
spyOn(dataService, "getSomeData").and.returnValue({$promise: getSomeDataDeferred.promise});
...
);
it(...
getSomeDataDeferred.resolve([{obj: "obj1"}, {obj: "obj2"}]);
$scope.$apply();
...
);
With the test described above, the console.log(data) would be testable as the data is accessible from being passed into the .then(). But the data is not immediately available from storeTheData, so storeTheData[0].obj would be undefined. On debug, I can see the data if I go through the promise that was attached to storeTheData via storeTheData.$$state.value
Like I said, I know I could use $httpBackend instead, but is there any way to do this with $q without changing the code under test?
I've not found a way to do this with $q.resolve, but I do have a solution that doesn't involve using the data service or changing the code under test. This is as good, because the main things I wanted to avoid were testing the data service as a side effect and changing the code.
My solution was to create a $resource object via $injector...
$resource = $inject.get("$resource");
...then return that in my basic implementation of the data service. This means I could use $httpBackend to respond to the request to an end point that isn't reliant on the data service's definition staying consistent.
dataService = {
getSomeData: function () {
/* new code starts here */
var resource = $resource(null, null, {
get: {
method: "GET",
isArray: true,
url: "/getSomeData"
}
});
return resource.get();
/* new code ends here */
}
};
...
$httpBackend.when("GET", "/getSomeData").respond(...;
Related
I'm trying to rewrite the code for http://m.amsterdamfoodie.nl in a more modern style. Basically single page Angular app downloads a set of restaurants with locations and places them on a map. If the user is the Amsterdam area then the user's location is added too, as are the distances to places.
At present I manage the asynchronous returns using a lot of if (relevant object from other async call exists) then do next step. I'd like to make more use of promises would be better.
So, flow control should be:
Start ajax data download, and geolocation call
if geolocation returns first, store coords for later
once ajax data is downloaded
if geolocation available
calculate distances to each restaurant, and pass control to rendering code
else pass control immediately to render code
if geolocation resolves later, calculate distances and re-render
The patterns I find on the internet assume that all async calls must return successfully before continuing, whereas my geolocation call can fail (or return a location far from amsterdam) and that's OK. Is there a trick I could use in this scenario or are the conditional statements really the way to go?
Every time you use .then, you essentially create a new promise based on the previous promise and its state. You can use that to your advantage (and you should).
You can do something along the lines of:
function getGeolocation() {
return $http.get('/someurl').then(
function resolveHandler(response) {
// $http.X resolves with a HTTP response object.
// The JSON data is on its `data` attribute
var data = response.data;
// Check if the data is valid (with some other function).
// By this, I mean e.g. checking if it is "far from amsterdam",
// as you have described that as a possible error case
if(isValid(data)) {
return data;
}
else {
return null;
}
},
function rejectHandler() {
// Handle the retrieval failure by explicitly returning a value
// from the rejection handler. Null is arbitrarily chosen here because it
// is a falsy value. See the last code snippet for the use of this
return null;
}
);
}
function getData() {
return $http.get('/dataurl').then(...);
}
and then use $q.all on both promises, which in turn creates a new promise that resolves as soon as all the given promises have resolved.
Note: In Kris Kowal's Q, which Angular's $q service is based on, you could use the allSettled method, which does almost the same as all, but resolves when all promises are settled (fulfilled or rejected), and not only if all promises are fulfilled. Angular's $q does not provide this method, so you can instead work your way around this by explicitly making the failed http request resolve anyways.
So, then you can do something like:
$q.all([getData(), getGeolocation()])
.then(function(data, geolocation) {
// `data` is the value that getData() resolved with,
// `geolocation` is the value that getGeolocation() resolved with.
// Check the documentation on `$q.all` for this.
if(geolocation) {
// Yay, the geolocation data is available and valid, do something
}
// Handle the rest of the data
});
Maybe I'm missing something... but since you have no dependencies between the two async calls, I don't see why you can't just follow the logic you outlined:
var geoCoordinates = null;
var restaurants = null;
var distances = null;
getRestaurantData()
.then(function(data){
restaurants = data;
if (geoCoordinates) {
distances = calculate(restaurants, geoCoordinates);
}
// set $scope variables as needed
});
getGeoLocation()
.then(function(data){
geoCoordinates = data;
if (restaurants){
distances = calculate(restaurants, geoCoordinates)
}
// set $scope variables as needed
});
I want to develop set of functions(sort of library) for CRUD in AngularJS so I can reuse them for couple of entities of my project. For server communication I made factory of $resource and using accordingly. $resource factory looks like this:
Model File:
var get_entity_model = angular.module("app.getentity", []).factory('getEntity', ['$resource', function($resource) {
return{
entity_view: $resource(baseurl+'/rest/'+serviceName+'/entity/:id/?app_name='+appName+'&fields=*', null, {'update': { method:'PUT' }})
}
}]);
And here how I'm using it in controller
Controller File:
getEntity.entity_view.get(
function(entity_list){
},
function(error){
}
)
Here entity_view is the table name. I'm passing all related functions like pagination and sub request to get the data of related tables etc code I put into success function of above request.
Now I want to make a library where I can define all this stuff and simply by calling the function I should be able to get all this stuff like:
entity.getEntity()
Should return same result as above code.
I tried with creating factory for above task but seems it need callback function and function at factory will return only data which I'm already getting from my model file so I need to make it compact and easy to use.
Factory Code at factory file:
var api = angular.module("app.entity_api", []).factory('entity_factory', ['$resource','getEntity',function($resource,getEntity) {
var entity_factory = {};
entity_factory.get_entity = function(callback){
getEntity.entity_view.get().$promise.then(
function(data){
callback(data.record);
}
);
}
return entity_factory;
}]);
And here how I call the function in controller:
Controller code:
api.controller("sample",['entity_factory','getEntity','$scope',function(entity_factory,getEntity,$scope){
$scope.init = function(){
entity_factory.get_entity(
function(data){
console.log(data);
}
);
}
$scope.init();
}])
Problem is that my entity_factory code will return only the data from server rest of the additional code I've to do in callback function which seems not much difference than my current exercise. So, the question is how can I achieve my goal to make a library of functions with additional code which return complete compiled result to make the code reusable for other entities and compact.
I like that you're a thinking of making a library but in this case, don't reinvent the wheel and save your precious time. Check out Restangular and your task will be a lot easier. Restangular is an AngularJS service that simplifies common GET, POST, DELETE, and UPDATE requests with a minimum of client code. It's a perfect fit for any WebApp that consumes data from a RESTful API.
I am trying to make Breeze.JS to make use of angular's http service for ajax calls. I followed the the docs (http://www.breezejs.com/documentation/customizing-ajax) and applied it. However it doesn't work.
Further more when I checked breeze source code I saw the following:
fn.executeQuery = function (mappingContext) {
var deferred = Q.defer();
var url = mappingContext.getUrl();
OData.read({
requestUri: url,
headers: { "DataServiceVersion": "2.0" }
},
function (data, response) {
var inlineCount;
if (data.__count) {
// OData can return data.__count as a string
inlineCount = parseInt(data.__count, 10);
}
return deferred.resolve({ results: data.results, inlineCount: inlineCount });
},
function (error) {
return deferred.reject(createError(error, url));
}
);
return deferred.promise;
};
It simply calls OData.read without doing anything about http service. Thus OData makes use of builtin ajax. I don't understand with above code, how it is possible to customize ajax of Breeeze.JS
The problem is that the Breeze OData path does NOT use the Breeze Ajax adapter. Changing the Breeze Ajax Adapter (as the "Breeze Angular Service" does) won't help.
At the moment, both the "OData" and "webApiOData" DataService Adapters delegate to the 3rd party datajs library for AJAX services (and for other OData-related support).
You could replace its odata.defaultHttpClient with a version of your own based on $http. That's not a trivial task. Look here for the source code; it's roughly 160 lines.
I suppose we could write one. It hasn't been a priority.
Until somebody does it or we abandon datajs (not soon if ever), you're stuck with the datajs ajax.
Sorry about that.
p.s. Just about everyone who talks to OData data sources uses the datajs library. Maybe you can talk to the authors of that library and try to get them to support$http.
Quick and dirty hack to simulate $http service
I ran into this issue today. Since the external datajs AJAX methods are used rather than Angular's $http service (as explained by Ward), Breeze queries do not trigger a digest and the models do not get updated.
As with any external-to-angular changes, the simple solution is to wrap any assignments from your queries in a $scope.$apply() function. However, this will quickly clutter up your app so it's a bad idea.
I came up with a quick and dirty hack that so far seems to work well:
I have a dataContextservice which encapsulates all my Breeze queries and exposes methods like getCustomers(), getProducts() etc (inspired by the example on the Breeze site).
When any of these data-access methods completes (ie the promise resolves), I call a triggerAngularDigest() method.
This method simple calls $rootScope.$apply() inside a $timeout().
The $timeout() causes Angular to run the digest on the next tick, i.e. after the data from your Breeze query has been assigned to your models.
All your models update just like when you use $http, no need to call $apply() in your controllers.
Simplified version:
function dataContext($rootScope, $timeout, breeze) {
// config of entity manager etc snipped
return {
getCustomers: function () {
return breeze.EntityQuery.from('Customers')
.using(manager)
.execute()
.then(function(data) {
triggerAngularDigest(); // <-- this is the key
return data;
});
}
};
function triggerAngularDigest() {
$timeout(function() {
$rootScope.$apply();
}, 0);
}
}
myApp.factory('dataContext', dataContext);
Then:
// some controller in your app
dataContext.getCustomers().then(function(data) {
scope.customers = data;
});
I am using AngularJS Services in my application to retrieve data from the backend, and I would like to make a loading mask, so the loading mask will start just before sending the request. but how can I know when the request ends?
For example I defined my servive as:
angular.module('myServices', ['ngResource'])
.factory('Clients', function ($resource) {
return $resource('getclients');
})
.factory('ClientsDetails', function ($resource) {
return $resource('getclient/:cltId');
})
So I use them in my controller as:
$scope.list = Clients.query();
and
$scope.datails = ClientsDetails.get({
date:$scope.selectedId
});
So the question would be, how to know when the query and get requests ends?
Edit:
As a side note in this question I've been using using angularjs 1.0.7
In AngularJS 1.2 automatic unwrapping of promises is no longer supported unless you turn on a special feature for it (and no telling for how long that will be available).
So that means if you write a line like this:
$scope.someVariable = $http.get("some url");
When you try to use someVariable in your view code (for example, "{{ someVariable }}") it won't work anymore. Instead attach functions to the promise you get back from the get() function like dawuut showed and perform your scope assignment within the success function:
$http.get("some url").then(function successFunction(result) {
$scope.someVariable = result;
console.log(result);
});
I know you probably have your $http.get() wrapped inside of a service or factory of some sort, but you've probably been passing the promise you got from using $http out of the functions on that wrapper so this applies just the same there.
My old blog post on AngularJS promises is fairly popular, it's just not yet updated with the info that you can't do direct assignment of promises to $scope anymore and expect it to work well for you: http://johnmunsch.com/2013/07/17/angularjs-services-and-promises/
You can use promises to manage it, something like :
Clients.query().then(function (res) {
// Content loaded
console.log(res);
}, function (err) {
// Error
console.log(err);
});
Another way (much robust and 'best practice') is to make Angular intercepting your requests automatically by using interceptor (see doc here : http://docs.angularjs.org/api/ng.$http).
This can help too : Showing Spinner GIF during $http request in angular
As left in a comment by Pointy I solved my problem giving a second parameter to the get function as following:
$scope.datails = ClientsDetails.get({
date:$scope.selectedId
}, function(){
// do my stuff here
});
I'm sending a model to server via $http.post, but, say, empty dates must be deleted, ids must be converted to int, in float values comma must be replaced with dot. These are restrictions of the server-side json api, so I'm looking for a way to modify $http request. Complicated part is that the modification rules depend on a api method name, which itself is a part of request.
The most straightforward way is to declare a modifying function and pass model to that function right before $http.post
$scope.method1Adapter = function(model) {
var data = angular.copy(model);
// 30 lines of modification code
return data;
};
$http.post("/api", {method: "method1", "data": $scope.method1Adapter($scope.data)})
but I think it's a little bit spaghettysh.
Better way is a factory that gets a method name and returns an adapter callback.
coreApp.factory("httpAdapter", function() {
return {
get: function (method) {
if (method == 'method1') {
return function (model) {
var data = angular.copy(model);
// modifications
return data;
}
} else {
return function (model) {
return model;
}
}
}
}
});
so i can add this to $httpProvider.defaults.transformRequest callbacks chain
coreApp.config(function($httpProvider, httpAdapterProvider) {
$httpProvider.defaults.transformRequest.unshift(function(post) {
if (post && post.data && post.data) {
post.data = httpAdapterProvider.$get().get(post.method)(post.method);
}
})
});
And still I don't like that, because api for my application has 16 methods, and it would require 5 adapters which is about 100 lines of code hard to maintain.
Any ideas about more clean and neat solution? Thank you.
I wouldn't chain adapters here because, as you said, it would be hard to maintain.
My advice would be to use the $http interceptors (not the responseInterceptors, which are deprecated, but the normal one, see http://docs.angularjs.org/api/ng.$http).
Notice in that you have access to the "config" object that has the request url, amongst other interesting properties.
It won't be superneat but at least the problem can be contained in one isolated part of your codebase.