Angular resource response interceptor - javascript

I want to create a Custom Action on Resource, which will process the results received from the server.
angular.module('problem', ['ngRoute', 'ngResource'])
.factory('Abc', function ($resource) {
return $resource('api/abc/:abcId', {abcId: '#id'}, {
customQuery: {
method: "GET",
isArray: true,
interceptor: {
response: function (response) {
// some operations that manipulate data based od response
response.data = [5, 6]; // for simplifity
console.log("manipulation finished"); // log is saved
return response;
}
}
}
}
);
})
;
But when I use the Custom Action, I get the unmodified results instead of processed.
Here is the code showing the expected behavior (and in comments relevant errors):
describe('Abc', function () {
beforeEach(module('problem'));
var $httpBackend;
beforeEach(function () {
angular.mock.inject(function ($injector) {
$httpBackend = $injector.get('$httpBackend');
})
});
it('should return converted array when customQuery called', inject(function (Abc) {
$httpBackend
.expectGET('api/abc')
.respond([
{id: 'uid1', name: 'name1'},
{id: 'uid2', name: 'name2'},
{id: 'uid3', name: 'name3'},
{id: 'uid4', name: 'name4'}
]);
var result = Abc.customQuery();
$httpBackend.flush();
expect(result.length).toBe(2); // fails with "Expected 4 to be 2."
expect(result[0]).toBe(5); // fails with "Expected { id : 'uid1', name : 'name1' } to be 5."
expect(result[1]).toBe(6); // fails with "Expected { id : 'uid2', name : 'name2' } to be 6."
}));
});

Maybe what you need is to add a new transformResponse transformation, you can easily do that like this (remember to inject $http):
transformResponse: $http.defaults.transformResponse.concat([
function (data, headersGetter) {
return data.objects
}
Difference is, result will be substituted by response.resource, while the return value of the interceptor is the value that is resolving result.$promise

thank you for the "interceptor" idea!
in my opinion
response.data = [5, 6]; // for simplifity
return response;
should return a response-object, with a property-array "data", so
result.length;
should fail.
For manipulating the results of a resource response better use response.resource instead of response.data - there are the real REST objects (with CRUD methods)

Related

JavaScript: Duplicate data property in object literal not allowed in strict mode

problem Description
I am dealing with an optional variable called ByteRange. For this purpose I added 2 different URLs in $resource.
When I did that I got the following error:
Message:
Error in parsing: "tools/test/retrieve/retrieve.service.js", Line 24: Duplicate data property in object literal not allowed in strict mode
Details:
domain: [object Object]
domainThrown: true
I understood that I can't have 2 gets. Is there another way to deal with optional variables in Javascript?
source code
(function () {
'use strict';
angular
.module('MyApp')
.factory('retrieve', retrieveObject);
retrieveObject.$inject = ['$resource'];
function retrieveObject($resource) {
this.resource = $resource('api/tools/test', {}, {
'list': {
method: 'GET',
url: 'api/tools/test/list/:chain/:path/:dataCenter'
},
'get': {
method: 'GET',
url: 'api/tools/test/get/:chain/:dataCenter/:path/:byteRange'
},
'get': {
method: 'GET',
url: 'api/tools/test/get/:chain/:dataCenter/:path'
},
});
return this;
}
})();
Okay this is not perfect but it must help you, probably use regex instead of replace is a better idea.
function resolveUrl(url, params) {
let resolvedUrl = url;
for(let i in params) {
resolvedUrl = resolvedUrl.replace(i, params[i]);
}
return resolvedUrl;
}
let params = {
':chain': 'aaa',
':dataCenter': 'bbb',
':path': 'ccc',
':byteRange': '',
};
let result = resolveUrl('api/tools/test/get/:chain/:dataCenter/:path/:byteRange', params);
console.log(result);
// Output : "api/tools/test/get/aaa/bbb/ccc/"
params = {
':chain': 'aaa',
':dataCenter': 'bbb',
':path': 'ccc',
':byteRange': 'yyy',
};
result = resolveUrl('api/tools/test/get/:chain/:dataCenter/:path/:byteRange', params);
console.log(result);
// Output : "api/tools/test/get/aaa/bbb/ccc/yyy"

Jasmine test fails in AngularJS factory

I have create in AngularJS the following basic factory that retrieves an array of objects:
angular.
module('core.beer-retriever').
factory('Beer', ['$resource',
($resource) => {
return $resource('https://www.xxxxxxxxx.com/v2/beers', {}, {
query: {
method: 'GET',
isArray: true
},
});
},
]);
I have created the following test with Jasmine:
describe('Beer Service', function() {
beforeEach(angular.mock.module('core.beer-retriever'));
beforeEach(inject(function($httpBackend) {
let beersRetrieved = {
results: [{
"id": 192,
"name": "Punk IPA 2007 - 2010",
}]
};
$httpBackend.whenGET('https://api.xxxxxxxxx.com/v2/beers')
.respond(beersRetrieved);
}));
it('is defined', inject(function(Beer) {
expect(Beer).toBeDefined();
}));
it('returns a resource function', inject(function(Beer) {
const output = Beer;
expect(output).toEqual(jasmine.any(Function));
expect(output.query({}.$promise)).toBeDefined();
}));
it('returns beers',
inject(function(Beer) {
const output = Beer.query();
expect(output.length).toEqual(1);
expect(output[0].id).toEqual(192);
httpBackend.flush();
}));
});
The first two tests are successful, but the third fails:
Expected 0 to equal 1.
TypeError: Cannot read property 'id' of undefined
at Object.<anonymous> (app/core/beer-retriever/beer-retriever.spec.js:29:25)
at Object.invoke (app/bower_components/angular/angular.js:4862:19)
at Object.WorkFn (app/bower_components/angular-mocks/angular-mocks.js:3170:20)
How can I figure out where the problem is?
Need to add a resolve function to your resource promise object.
checkout this working plunker
output.$promise.then(function(data) {
console.log(data);
output = data[0].results;
});
And full spec looks as shown below:
it('returns beers',
inject(function(Beer, $httpBackend) {
var output = Beer.query();
output.$promise.then(function(data) {
console.log(data);
output = data[0].results;
});
$httpBackend.flush();
console.log('a11: ' + JSON.stringify(output));
expect(output.length).toEqual(1);
console.log(output[0].id);
expect(output[0].id).toEqual(192);
}));

Why is $httpProvider.interceptors returning an `undefined` value

I've created a basic AngularJS app that consumes Yelp's API and am having trouble using $httpProvider.interceptors to parse my response.
Here is my app:
var app = angular.module("restaurantList", []);
My yelpAPI service (not pictured) authenticates my API request and generates a HTTP request. I then output the data received to the Web Console like so:
app.controller("mainCtrl", ["$scope", "yelpAPI", function ($scope, yelpAPI) {
$scope.restaurants = [];
yelpAPI.get(function (data) {
$scope.restaurant = data;
console.log($scope.restaurant);
});
}]);
Here is data from my request:
Object {region: Object, total: 37, businesses: Array[20]}
I want the array located in the businesses property. So, I figured it would be a good idea to use $httpProvider.interceptors to parse the objects found, in the Object.businesses array.
Here is what $httpProvider.interceptors looked like, when I made my initial request:
app.config(function ($httpProvider) {
$httpProvider.interceptors.push(function () {
return {
response: function (response) {
return response;
}
}
});
});
Here is what $httpProvider.interceptors looks like now:
app.config(function($httpProvider) {
$httpProvider.interceptors.push(function() {
return {
response: function(response) {
var old_response = response.businesses,
new_response = [];
for (var i = 0; i < old_response.length; i++) {
var obj = old_response[i],
new_obj = {
restaurant_name: obj.name,
phone_number: obj.display_phone,
yelp_rating: obj.rating,
reservation_url: obj.reservation_url
};
new_response.push(new_obj);
}
return new_response;
}
}
});
});
Now, I'm receiving an error that says TypeError: Cannot read property 'businesses' of undefined. Is there something I'm overlooking?
EDIT #1
I used console.log(response) inside the interceptor to print my response and found that response.businesses should actually be response.data.businesses. Which resolves my error but now my $http call returns undefined. Any idea what my new problem could be?
EDIT #2
app.factory("yelpAPI", function($http, nounce) {
return {
get: function(callback) {
var method = "GET",
url = "http://api.yelp.com/v2/search";
var params = {
callback: "angular.callbacks._0",
oauth_consumer_key: "my_oauth_consumer_key",
oauth_token: "my_oauth_token",
oauth_signature_method: "HMAC-SHA1",
oauth_timestamp: new Date().getTime(),
oauth_nonce: nounce.generate(),
term: "American",
sort: 2,
limit: 20,
radius_filter: 4000,
deals_filter: true,
actionlinks: true
};
var consumerSecret = "my_consumer_secret",
tokenSecret = "my_token_secret",
signature = oauthSignature.generate(method, url, params, consumerSecret, tokenSecret, {
encodeSignature: false
});
params["oauth_signature"] = signature;
$http.jsonp(url, {
params: params
}).success(callback);
}
}
});
In return angular wait object with {data : }:
app.config(function($httpProvider) {
$httpProvider.interceptors.push(function() {
return {
response: function(res) {
var old_response = res.businesses,
new_response = [];
for (var i = 0; i < old_response.length; i++) {
var obj = old_response[i],
new_obj = {
restaurant_name: obj.name,
phone_number: obj.display_phone,
yelp_rating: obj.rating,
reservation_url: obj.reservation_url
};
new_response.push(new_obj);
}
return {data : new_response};
}
}
});
});
Return as {data : new_response}
Emir helped me resolve this issue but essentially, whenever you use $httpProvider.interceptors you have to update response.data and return the entire response object (i.e. not just a new array, as I did) because $http selects the data property for you.

Why I'm getting the var undefined

I'm trying to get a Rest array and then display it on my HTML.
the problem that I'm facing is that In My factory I'm able to get the array and display it's content, but not in my controller where I'm always getting the Undefined var. here is my Factory
.factory('coursesService', function($resource) {
var service = {};
service.getAllCouses = function (){
var Courses = $resource("/myproject/rest/training/trainings");
var data = Courses.query().$promise.then(function(data)
{
service.data= data;
console.log("ligne 1: ", service.data[0].name);
console.log("ligne 1: ", service.data[0].creator);
console.log("ligne 2: ", data[1].name);
console.log("ligne 2: ", data[1].creator);
return service.data;
});
}
return service;
})
and my controller
.controller("CoursesController",
function CoursesController($scope, coursesService) {
var courses = {};
courses = coursesService.getAllCouses();
console.log("courses: ", courses);
})
as results I'm getting this:
courses: undefined
ligne 1: Angular
ligne 1: Object {id: "1", username: "User1", email: "user1#gmail.com", password: "password", userProfile: Object}
ligne 2: JavaScript
ligne 2: Object {id: "1", username: "User1", email: "user1#gmail.com", password: "password", userProfile: Object}
Why I'm getting courses: undefined? and Shouldn't be displayed after the list that I'm displaying in the factory?
Your getAllCouses function never returns anything, and so calling it always results in undefined. The callback to the query promise then handler returns something, but not getAllCouses.
You'll need to have getAllCouses return the promise, and then use the result from within a then handler on that promise. You can't just use its return value directly, not if Courses.query() is async (and if it isn't, why is it returning a promise?).
That would look something like this:
.factory('coursesService', function($resource) {
var service = {};
service.getAllCouses = function (){
var Courses = $resource("/myproject/rest/training/trainings");
var data = Courses.query().$promise.then(function(data) {
service.data= data;
console.log("ligne 1: ", service.data[0].name);
console.log("ligne 1: ", service.data[0].creator);
console.log("ligne 2: ", data[1].name);
console.log("ligne 2: ", data[1].creator);
return service.data;
});
return data; // <=== Return the promise (`data` is a bit of a suspect name)
};
return service;
})
Then:
.controller("CoursesController", function CoursesController($scope, coursesService) {
coursesService.getAllCouses().then(function(courses) { // <=== Use the promise
console.log("courses: ", courses); // <===
}); // <===
})
...but I'm not an Angular guy.
getAllCouses() does not return a value, it just sets two local variables, Courses and data.
The Courses.query promise only resolves once the web request is complete, so those logs are delayed.
I fixed the issue by updating my service and controller like this:
.factory('coursesService', function($resource) {
return $resource("/myproject/rest/training/trainings", {
query : {
method : "GET",
isArray : true
}
});
});
I just added the
isArray : true
and i updated the controller
.controller(
"CoursesController",
function UserController($scope, coursesService) {
coursesService.query(function(data) {
console.info("data: ", data)
console.log("1st course: ", data[0].name)
$scope.Courses = data;
});
I fix it simply in this way:
.factory('coursesService', function($resource) {
return $resource("/myproject/rest/training/trainings")
})
and the controller:
.controller("coursesController", function($scope, coursesService) {
coursesService.query(function(data) {
$scope.Courses = data;
});
})

Testing a loader in angular js

This is a test for a $resource with a loader
describe('Service: MultiCalculationsLoader', function(){
beforeEach(module('clientApp'));
var MultiCalculationLoader,
mockBackend,
calculation;
beforeEach(inject(function (_$httpBackend_, Calculation, _MultiCalculationLoader_) {
MultiCalculationLoader = _MultiCalculationLoader_;
mockBackend = _$httpBackend_;
calculation = Calculation;
}));
it('should load a list of calculation from a user', function(){
mockBackend.expectGET('/api/user/600/calculation').respond([{id:5}]);
var calculations;
var mockStateParams = {
userId: 600
};
var promise = new MultiCalculationLoader(mockStateParams);
promise.then(function(res){
calculations = res
});
expect(calculations).toBeUndefined();
mockBackend.flush();
expect(calculations).toEqual([{id:5}]);
});
});
When I run the test I get the following error:
Expected [ { id : 5 } ] to equal [ { id : 5 } ].
Error: Expected [ { id : 5 } ] to equal [ { id : 5 } ].
at null.<anonymous>
I don't get it. The two arrays are the same to me. Ideas anyone?
Update
Here's the implementation:
.factory('Calculation', function ($resource) {
return $resource('/api/user/:userId/calculation/:calcId', {'calcId': '#calcId'});
})
.factory('MultiCalculationLoader', function (Calculation, $q) {
return function ($stateParams) {
var delay = $q.defer();
Calculation.query( {userId: $stateParams.userId},function (calcs) {
delay.resolve(calcs);
}, function () {
delay.reject('Unable to fetch calculations');
});
return delay.promise;
};
})
The url you expect is different from the actual url. I guess you need something like this:
it('should load a list of calculation from a user', function(){
//remove the 's' from 'calculations'
mockBackend.expectGET('/api/user/600/calculation').respond([{id:5}]);
var calculations;
var promise = MultiCalculationLoader({userId:600}); //userId = 600
promise.then(function(res){
calculations = res
});
expect(calculations).toBeUndefined();
mockBackend.flush();
expect(calculations).toEqual([{id:5}]);
});
There is another problem that angular automatically adds 2 properties to the response:
http://plnkr.co/edit/gIHolGd85SLswzv5VZ1E?p=preview
It's kind of the same problem as: AngularJS + Jasmine: Comparing objects
This is really a problem with angular $resource when angular convert the response to resource objects. In order to verify responses from $resource, you could try angular.equals
expect(angular.equals(calculations,[{id:5},{id:6}])).toBe(true);
http://plnkr.co/edit/PrZhk2hkvER2XTBIW7yv?p=preview
You could also write a custom matcher:
beforeEach(function() {
jasmine.addMatchers({
toEqualData: function() {
return {
compare: function(actual, expected) {
return {
pass: angular.equals(actual, expected)
};
}
};
}
});
});
And use it:
expect(calculations).toEqualData([{id:5},{id:6}]);
http://plnkr.co/edit/vNfRmc6R1G69kg0DyjZf?p=preview
Jasmine equality selectors can be too specfic sometimes when you just wanna check for equaity.
I have never seen it with the toEqual() method when comparing objects or arrays, but def with the toBe() method.
Try to replace toEqual() with toMatch().
Also in the unit test I recommend using a constant value that you can pass in the response and the matchers/equal/toBe's.
describe('Service: MultiCalculationsLoader', function(){
beforeEach(module('clientApp'));
var MultiCalculationLoader,
mockBackend,
calculation,
VALUE = [{id:5}];
beforeEach(inject(function (_$httpBackend_, Calculation, _MultiCalculationLoader_) {
MultiCalculationLoader = _MultiCalculationLoader_;
mockBackend = _$httpBackend_;
calculation = Calculation;
}));
it('should load a list of calculation from a user', function(){
mockBackend.expectGET('/api/user/600/calculations').respond(VALUE);
var calculations;
var promise = MultiCalculationLoader();
promise.then(function(res){
calculations = res
});
expect(calculations).toBeUndefined();
mockBackend.flush();
expect(calculations).toEqual(VALUE);
});
});
Using this approach I think the .toEqual will actually work.
Our approach:
Before Block:
httpBackend.when('JSONP', PATH.url + 'products?callback=JSON_CALLBACK&category=123').respond(CATEGORIES[0]);
Test:
describe('Category Method', function () {
it('Should return the first category when the method category is called', function () {
var result = '';
service.category(123).then(function(response) {
result = response;
});
httpBackend.flush();
expect(result).toEqual(CATEGORIES[0]);
});
});
you can try change toEqual() with toEqualData()
expect(calculations).toEqualData([{id:5}]);

Categories

Resources