Angular $httpProvider transformResponse data contains local HTML DOM elements? - angularjs

When I instantiate the following code in an AngularJS app, I get weird data in the transformResponse function (bottom of code). I'm not calling the $resource function from any controller, just loading the script in a browser. The data variable (see code) contains the HTML of the current partial, when loading the app.
This seems odd. Is this the way it's supposed to be?
var buddyServices = angular
.module('buddyServices', ['ng','ngResource'])
.factory('Buddy',
function ($resource) { console.log('resource');
return $resource('http://webservice.buddyplatform.com/v1/:service',
{service:'', BuddyApplicationName: 'xxx',
BuddyApplicationPassword: 'yyy'}
);
}
)
.config(function($httpProvider){
delete $httpProvider.defaults.headers.common['X-Requested-With'];
$httpProvider.defaults.transformResponse = function(data) {
console.log(data);
return 'TEST: '+data;
};
});
=== EDIT ===
It just daunted on me: $httpProvider handles all http requests, so a page load is one of those. I'm guessing a bit now, but it seems probable. So, the question then becomes: Is there an "easy" way to constrain the data in the code above to only those requests performed by my service?

transformResponse takes another parameter headersGetter. You can use this to get the headers send with the response. Look for Content-Type header header. It should contain application/json

Related

BreezeJS datacontext POST in angular turns into 'GET'

I have a factory to retrieve json from my own server via POST, I'm getting the errror "TypeError: Cannot read property '$http' of undefined {query: ctor, stack: (...), message: "Cannot read property '$http' of undefined"}". Walking through breeze.debug.js I see my request is turned into a GET and thus mungs up the call. How do I force Breeze to execute my POST? Below is the datacontext based on the BreezeJS documentation on doing posts at http://www.getbreezenow.com/documentation/breezeajaxpostjs :
angular.module('rmBreezeApp.datacontext', [])
.factory('datacontext', function ($http, breeze, jsonResultsAdapter, logger, model, RMServer,AuthService) {
breeze.ajaxpost();
var postData = function (selector, argsArray) {
return {"selector": selector, "arguments": argsArray}
};
var ds = new breeze.DataService({
serviceName: RMServer,
hasServerMetadata: false,
useJsonp: false,
jsonResultsAdapter: jsonResultsAdapter
});
var manager = new breeze.EntityManager({dataService: ds});
model.initialize(manager.metadataStore);
return {
getShiftsForWeek: getShiftsForWeek
};
/*** implementation details ***/
function getShiftsForWeek() {
var arguments = [];
var query = breeze.EntityQuery.from("session?token=" + AuthService.authToken())
.withParameters({
$method: 'POST',
$encoding: 'JSON',
$data: postData("getShiftsForWeek",[])
});
return manager.executeQuery(query).then(
function (data) {
return data;
},
function (reject) {
console.log(reject);
}
)
}
});
I've got these includes in my index.html
<!-- ionic/angularjs js -->
<script src="lib/ionic/js/ionic.bundle.js"></script>
<script src="lib/breezejs/breeze.debug.js"></script>
<script src="lib/breeze.js.labs/breeze.angular.js"></script>
<script src="lib/breeze.js.labs/breeze.ajaxpost.js"></script>
Good thing you wrote a plunker. It revealed a number of errors and opportunities which I'll summarize here.
I forked your plunker and "fixed it". It's now returning data and displaying on screen.
I tried not to change too much but I did re-organize for what seems to me a readable style. I begin by showing the complete application component set at the top:
angular.module('myApp', ['breeze.angular'])
.value('jsonResultsAdapter', createJsonResultsAdapter())
.service('datacontext', DataContext)
.service('model', Model)
.controller('sitesCtrl', SitesCtrl)
//.config(configForCORS);
Observations
CORS
Your attempt to configure the browser for CORS (configForCors) makes no difference to my browsers (IE10, Chrome, FF). I don't think it will help you. Either a browser supports CORS or it doesn't. I haven't found any way around that. You're in trouble if you need to support IE8 because it doesn't do CORS.
Configure ajaxAdapter for Breeze
The 'breeze.angular' module does that for you. Including that module as a dependency (angular.module('myApp', ['breeze.angular'])) and then injecting the 'breeze' service into the first service that needs breeze (e.g., Datacontext) is all you needed to do.
Configure Breeze for POST queries
Breeze by default sends query requests as GET requests. Your service expects POST requests. Breeze can do that.
You neglected to include the breeze.ajaxpost.js library script tag in your index.html. You need that Breeze Labs plug-in to make POST queries.
You also have to tell the Angular ajaxAdapter about it. Look in DataContext#configAjaxAdapter. You'll see:
// configure that adapter to use ajaxPost plugin
breeze.ajaxpost(ajaxAdapter);
Actually, you don't need to specify the ajax adapter; breeze will enable POST query support for whatever is the current default ajax adapter instance if you write this:
// configure that adapter to use ajaxPost plugin
breeze.ajaxpost();
But I was more explicit because I had to get the ajax adapter anyway in order to set your default headers:
// default ajax adapter is already a wrapper around current $http
// as a result of injecting the breeze service.
var ajaxAdapter = breeze.config.getAdapterInstance("ajax");
// add the app-specific default headers
// no need to set "Content-Type" for JSON; breeze does that by default
ajaxAdapter.defaultSettings = {
headers: {
"JsonStub-User-Key": "2d3b6e81-b556-4049-ab54-ec8422237c63",
"JsonStub-Project-Key": "a753777a-bbff-4db6-8755-ea8c5e60f032"
}
};
Notice I called getAdapterInstance, not initializeAdapterInstance. We want to modify the existing instance created by breeze.angular, not overwrite it with a new one.
Client metadata and jsonResultsAdapter
After making those changes, I was getting data from the service. Unfortunately, the
shape of that data didn't quite match the expectations in the jsonResultsAdapter or the metadata.
The JSON arrives in an object with a pathwaySchool property. That property returns an array. The array contains the entity data of interest; the array itself is useless and should not become a Breeze entityType.
I trained the 'jsonResultsAdapter.visitNode' method to recognize objects with a "buildingCode" property. Such objects contain the data for the PathwaySchool entities ... and we say so by returning {entityType: "PathwaySchool"} for those nodes.
Patrick's metadata describe a type named "Site". But his jsonResultsAdapter called the type "PathwaySchool". Had to call it one thing or the other. I chose "PathwaySchool".
The metadata also said we should expect an "_id" property that is the entity key. There was no such property in the data. The buildingCode property looked most like a unique identifier so I made that property the key. I also added a name property so we can display the school on screen.
Extracting entities from the ajax results
The service returns a deeply nested object. The datacontext should hide that mess from the ViewModel/Controller.
So the success callback digs in and extracts the array of "PathwaySchool"s.
return data.results[0].pathwaySchool;
We should do something if the ajax call fails. The fail callback writes the error to the console (using Angular's $log service for future testability) and then re-rejects the error.
Re-rejecting is important! We want to make sure the caller sees the error so it can process it appropriately. If we did nothing, the Angular would assume we had fixed the error and pass undefined to the caller's success callback. That's not what we want. We want to pass the error along to the caller's fail callback. So we re-reject the error and return that (failed) promise.
ViewModel/Controller
Patrick's controller called the datacontext and simply assigned the results of the call to the $scope.sites array, expecting angular to bind to those results:
$scope.sites = datacontext.getSites(); // Not good!
That won't work. We don't want Angular to display the promise. We want to set the $scope.sites array after the promise resolves with the appropriate entities.
We should also be prepared to display an error if our call for data fails.
Here's how I get the sites into $scope:
$scope.sites = [];
datacontext.getSites()
.then(function(sites){
$scope.error = ''; // clear previous error if any
$scope.sites = sites;
})
.catch(function(error){
$scope.error = "'getSites' failed: " + error.message +
". Check console log for details.";
});
The View can bind to $scope.error if there's a problem.
View Binding
Finally, we display the results or error on screen. I need a small amount of HTML for the purpose:
<div ng-controller="sitesCtrl">
<div ng-repeat="site in sites">#{{site.buildingCode}} {{site.name}}</div>
<p class="errorMessage" ng-if="error">{{error}}</p>
</div>
I added a passing test that looks like yours to the ngDocCode sample:
var resource = '/breeze/Northwind/CustomersWithFilterOptions?token=1234ABCD';
beforeEach(function () {
breeze.ajaxpost(); // add POST option to ajax adapter
em = newEm(); // fresh EntityManager before each test
});
...
it("by CompanyName w/ appended URL query string", function (done) {
// See SO question
// http://stackoverflow.com/questions/27364078/breezejs-datacontext-post-in-angular-turns-into-get
var companyName = 'Die Wandernde Kuh';
// Notice the query string appended to the resource
EntityQuery.from(resource + '?token=1234ABCD')
.withParameters({
$method: 'POST',
$encoding: 'JSON',
$data: {
CompanyName: companyName
}
})
.using(em).execute()
.then(success)
.then(done, done);
function success(data){
expect(data.results).to.have.length(1);
var cust = data.results[0];
expect(cust.CompanyName).to.equal(companyName);
}
});
The request over the network looks like this:
POST /breeze/Northwind/CustomersWithFilterOptions?token=1234ABCD HTTP/1.1
Host: localhost:58066
Connection: keep-alive
Content-Length: 35
Pragma: no-cache
Cache-Control: no-cache
Accept: application/json, text/plain, */*
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko)
Content-Type: application/json;charset=UTF-8
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.8
The request payload is
{"CompanyName":"Die Wandernde Kuh"}
I'm not sure what's different between ours and yours. But the ball is in your court to provide a repro (plunker or jsFiddle).
I didn't see anything glaringly obvious in your question's code. I don't know why you're injecting $http but that shouldn't hurt.
I don't see where you're taking the dependency on the breeze labs 'breeze.angular' module but you must be doing it somewhere or Angular would have failed ("can't find breezeProvider") when it injected "breeze" into your "datacontext" factory.

Can I use $http to get a javascript for my web page?

I am using the following code:
var xmlhttp = new XMLHttpRequest();
xmlhttp.open("GET", "/Scripts/Pages/Home.js", false);
xmlhttp.setRequestHeader("X-Custom-Header", "My Values");
xmlhttp.send();
var m = document.createElement('script');
m.appendChild(document.createTextNode(xmlhttp.responseText));
document.getElementsByTagName('head')[0].appendChild(m);
Can someone advise me if it is possible to get a javascript with $http and show me how I can do it inside a function that returns a promise when it is completed. The reason I would like to use $http is that along with the request for the js I need to send a custom header for authorization.
Please note that this question is different from the one suggested as a duplicate in that I am also wanting to find out if I can get a javascript and add it to the page DOM in the same way as it was done with the .setRequestHeader. Thanks
Since $http is a implementation for XMLHttpRequest in Angular, you can of course make requests to get the contents of a JS file.
You can set additional headers with $http like this:
$http({
method: 'get',
url: 'some/js/file.js',
headers: {
"X-Custom-header": "foo"
}
}).then(function (data) {
// do something with the DOM here
});
So as you can see, you are actually able to do that.

How can I build a get request with url that contains parameters

I am trying to do a get request to recover data from the server using the following url:
url = /api/projects/:projectId/scenarios
How can I do that using $http.get in AdngularJS?.
you might want to take a look at Restangular, I like its notation more than standard angularjs $http or $resource.
Restangular.setBaseURL('/api/');
Restangular.one('projects', projectId).all('scenarios').getList().then(function(data) {
myVariable = data;
});
Restangular

AngularJS Execute function after a Service request ends

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
});

angularjs $http.jsonp goes to .error instead of .success even when 200 is received

I'm building an AngularJS app which calls a NodeJS server that gets data from a DB.
The NodeJS returns a JSON.stringify(someArrayWithData).
Here is the AngularJS code:
$scope.getMainGroups = function(){
$http.jsonp("http://127.0.0.1:3000/get/MainGroups?callback=JSON_CALLBACK").success(function(data, status, headers, config) {
$scope.MainGroups = data;
}).error(function(data, status, headers, config) {
$scope.MainGroups = status;
});
};
$scope.MainGroups is going to the .error instead to the success even when I can see in the chrome debugger that the result came back (200 ok).
What am I missing?
You must return a valid JSON response on the server side. The 200 OK is just the GET request that is injected into the DOM. I bet you are not returning a valid JSON response from the server with right response code.
Here is an example on how to construct the response on the server side (PHP):
Simple jQuery, PHP and JSONP example?
As answered here you need to change the NodeJS response. The JSONP answer needs to start with the exact text that AngularJS added in the URL (for example 'angular.callbacks._1').
// changes in your server script used by NodeJS
// Required for JSONP calls, 'callback' matches the '?callback=JSON_CALLBACK'
app.set('jsonp callback name', 'callback');
// ... in your response change json to jsonp
res.jsonp(votes);
After doing these changes, when the server detects a 'callback' in the parameters, answers like this:
/**/ typeof angular.callbacks._1 === 'function' &&
angular.callbacks._1([{"votes":3,"title":"new topic", etc...
Now yes, AngularJS correctly calls back the 'success' function.

Resources