Restangular using app context inconsistently - angularjs

I have an issue with Restangular using my sites context inconsistently. I have a site running at example.com/app-name. When making the below call I am expecting it to hit example.com/app-name/foo
angular.module('myApp').factory('File', File);
File.$inject = [ 'Restangular' ];
function File(Restangular) {
var service = {
postFiles : postFiles
};
return service;
function postFiles(files) {
return Restangular.all('foo').post(files);
}
}
However, it hits example.com/foo instead.
While not ideal, I figured I would set the base url for now to work through this issue and then just create a build step to handle the different environment app names.
angular.module('myApp').run(run);
run.$inject = [ 'Restangular' ];
function run(Restangular) {
Restangular.setBaseUrl('app-name');
}
After this though, it now hits example.com/app-name/app-name/foo.
My current solution to the problem is to do
Restangular.one('app-name').all('foo').post(files)
Which works but, again, will require some build tasks setup to make configurable which is less than ideal.
I've also tried setting the <base> tag to the following:
<base href="/app-name/" />
And the requests still go to example.com/foo instead of example.com/app-name/foo.
Is there something I'm missing in the setup or use of Restangular? Why is the app context being used differently in these two scenarios?

Related

GET call failing in AngularJS resource - Strange data result

I have a simple angular resource that I've defined as below:
CompanyService.factory('CompanyService',
function ($resource) {
return $resource('https://baseurl.com/api/values/');
}
);
I then have a controller that calls that resource passing in a success and fail function:
.controller('companyList', function($scope, CompanyService) {
$scope.companies = CompanyService.query(
function(data) {
console.log(data);
return data;
},
function(error){
console.log("Error:");
console.log(error);
}
);
The rest API is a .NET MVC Web API that is extremely basic. I've configured it to return JSON and it simply returns an array of two objects like below. I've also enabled CORS so my angular app, which is hosted in a different domain, can call the api.
[{ID:1, Name:"TEST1"}, {ID:2, Name:"TEST2"}]
I've tested the REST call using jquery and just straight call through browser. All was functional (including the cross site scripting when calling from my angular app just using a straight JavaScript HTTP call).
When I try to call the api from my controller however, it always ends up in the error function. The error object contains a data property that is always populated with the string "resource is required|resource is required|undefined"
When I check the network I see no call to the values end point. It's as if the call is failing before ever being made.
If I change out the url to point to some sample REST api like https://jsonplaceholder.typicode.com/users/ it works fine and I'm able to see the call to "users" in the network traffic, which makes me think there is something wrong with my C# REST endpoint, however all my tests to call the REST endpoint outside of angular work successfully.
Can anyone help? I can't find anyone reporting this issues before anywhere on the net.
should the code be the one below? i didn't test it, just guess.
myModule.factory('CompanyService',
function ($resource) {
return $resource('https://baseurl.com/api/values/');
}
)
.controller('companyList', function($scope, CompanyService) {
CompanyService.query(
function(data) {
$scope.companies = data;
console.log(data);
return data;
},
function(error){
console.log("Error:");
console.log(error);
}
);
I ended up rebuilding my angular app from scratch. My first app was from the angular-seed github and had a handful of libraries already added in for testing and other things. One of those things is was was leading to this error as once I started a new project completely from scratch and added in angular and my REST call things worked perfectly. I've already spent too much time working through this so not going to spend any more time identifying exactly what it is but in case anyone else runs into the problem I did want to answer this one and close the book on it.

Angular, without a browser?

Sometimes I want to experiment (in a local Node script) with some aspect of Angular - e.g. Services, DI, etc - stuff that has nothing to do with the Browser or the DOM. Is there a way to do that? i.e. load some base portion of the Angular Infrastructure? If I just require("angular") in a Node script, it complains:
ReferenceError: window is not defined
which makes sense because Angular lives for the Browser-window.
But it seems like some portions of Angular could be used for non-web applications - although that's not my reason for asking this. I'm just trying to improve my understanding Angular and sometimes want to do a little experiment while stripping away/ignore as much as possible.
Experimenting with Angular is best done in a browser, due to window and other API's Angular relies on.
However, if you're dead set on using Angular with node, you might look into the vm module which essentially lets you eval code with a specific stand-in object as a sort of proxy global object. e.g.:
const vm = require('vm');
const fs = require('fs');
const test = fs.readFileSync('./test.js', 'utf-8');
const windowProxy = {
document: {
createElement: function() {
return {
setAttribute: function() {},
pathname: ''
}
},
querySelector: function() {},
addEventListener: function() {}
},
location: {
href: ''
},
addEventListener: function() {}
};
windowProxy.window = windowProxy;
vm.createContext(windowProxy);
vm.runInContext(test, windowProxy);
will at least let you load Angular without complaining. Undoubtedly you would encounter more errors, and would have to polyfill the missing browser API's yourself.
You might also look into PhantomJS for a more robust testing environment, though that would no longer be Node.

Modify or access angular interceptors after config phase

Is it possible to access / modify $http interceptors after the config phase? I'm debugging an app that only breaks in production due to being deployed on a different server, so unfortunately I can't change the interceptor code locally and figure out what's going on.
If it's not possible to access / modify the interceptors, perhaps it'd be possible to replace $http. Here's an example of replacing a hypothetical service:
var inj = angular.element('body').injector(),
oldGet = inj.get,
mockService = { secret: 'shhh' };
inj.get = function(str) {
if (str === 'some-service') {
return mockService;
} else {
return oldGet.apply(inj, arguments);
}
};
However, I'm not sure how I'd go about creating a new $http service (into which I could pass in the modified interceptors). I can't grab the $httpProvider, either.
Perhaps bootstrapping a new ng-app on a separate part of the page would work? Then I could grab the $http service and replace it, like above.
Other ideas:
With reference to: Right way to disable/remove http interceptors in Angular? , it does not seem like I can access the interceptors array if I don't hold on to it in the config phase.
Perhaps I can use grease monkey to inject something that runs in the config phase.
Thank you!

angular $resource works with IIS express but not IIS ? I think something is wrong with the PUT?

I have isolated the problem down to a few lines. With IIS express it calls the PUT on the web API. When I switch to using IIS with the same code the call to the PUT method never happens.. The GET call works with both just fine.. any idea?
$scope.save = function (msa) {
$scope.msa = msa;
var id = this.msa.PlaceId;
Msa.update({ id: id }, $scope.msa, function () {
alert('finished update'); //only gets here with iis express
$scope.updatedItems.push(id);
$location.path('/');
});
}
MsaApp.factory('Msa', function ($resource) {
return $resource('/api/Place/:id', { id: '#id' }, { update: { method: 'PUT' } });
});
EDIT 1:
I thought it was working but now it only works when 'localhost' and not the computer name.. it is not calling the server method.. any ideas what things to look out for that make the site act differently from localhost to ? .. and even stranger.. the angular site wont load in IE.. but it loads in chrome
EDIT 2:
I think I have the answer.. The dewfault webapi PUT/UPDATE creates invalid code.. It sort of randomly would breaking at db.Entry(place).State = EntityState.Modified... I found code here that seems to fix it so far.. not exactly sure what it does though
An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key
Remove WebDAV module from IIS, it should work
IIS does block some of the actions by default, I believe PUT is one (DELETE is another).
See: https://stackoverflow.com/a/12443578/1873485
Go to Handler Mappings in your IIS Manager. Find ExtensionlessUrlHandler-Integrated-4.0, double click it. Click Request Restrictions... button and on Verbs tab, add both DELETE and PUT.

AngularJS HTML5 mode degrade to full page reloads in lieu of hashbang

By enabling HTML5 mode in AngularJS, the $location service will rewrite URLs to remove the hashbang from them. This is a great feature that will help me with my application, but there is a problem with its fallback to hashbang mode. My service requires authentication, and I am forced to use an external authentication mechanism from my application. If a user attempts to go to a URL for my app with a hashbang in it, it will first redirect them to the authentication page (won't ever touch my service unless successfully authenticated), and then redirect them back to my application. Being that the hash tag is only seen from the client side, it will drop off whatever parts of the routes come after by the time they hit my server. Once they are authenticated, they may re-enter the URL and it will work, but its that one initial time that will cause a disruption to the user experience.
My question is then, is there any way to go from $location.html5Mode(true) to the fallback of full page reloads for un-supportive browsers, skipping the hashbang method of routing entirely in AngularJS?
The best comparison of available implementations of what I'm aiming for would be something such as browsing around folders on github.com. If the browser supports rewriting the URL without initiating a page refresh, the page will asynchronously load the necessary parts. If the browser does not support it, when a user clicks on a folder, a full-page refresh occurs. Can this be achieved with AngularJS in lieu of using the hashbang mode?
DON'T overwrite the core functionality.
Use Modernizr, do feature detection, and then proceed accordingly.
check for history API support
if (Modernizr.history) {
// history management works!
} else {
// no history support :(
// fall back to a scripted solution like History.js
}
Try to wrap $location and $routeProvider configuration in browser's HTML5 History API checking, like this:
if (isBrowserSupportsHistoryAPI()) {
$location.html5Mode(true)
$routeProvider.when(...);
}
Also may be you need to create a wrapper to $location if you use it to change path.
(Sorry for terrible english)
Why not handle the un-authenticated redirect on the client side for this situation? I'd need to know a bit more about exactly how your app works to give you a more specific solution but essentially something like:
User goes to a route handled by AngularJS, server serves up the AngularJS main template and javascript
User is not authenticated, AngularJS detects this and redirects to the authentication page
You could have something in the module's run block for when the AngularJS application starts:
module('app',[])
.configure(...yadda...yadda...yadda...)
.run(['$location', 'authenticationService', function($location, auth) {
if (!auth.isAuthenticated()) {
$location.url(authenticationUrl)
}
});
I've subbed in a service which would find out if you were authenticated somehow, up to you how, could be checking a session cookie, could be hitting your API to ask. Really depends on how you want to continue to check authentication as the client application runs.
You can try and override the functionality of the $location service. The general idea would be to rewrite the URL according to whether someone is already authenticated or not, or just use a single approach (without hashbangs) for all URLs, regardless to whether html5mode is on or not.
I'm not sure that I fully understand the use-case so I can't write the exact code that you need. Here is a sample implementation of how to overrides/implements and registers the $location service, just making sure that hashbang is always eliminated:
app.service('$location', [function() {
var DEFAULT_PORTS = {
ftp: 21,
http: 80,
https: 443
};
angular.extend(this, {
absUrl: function() {
return location.href;
},
hash: function(hash) {
return location.hash.substr(1);
},
host: function() {
return location.host;
},
path: function(path) {
if (!path) {
return location.pathname;
}
location.pathname = path;
return this;
},
port: function() {
return location.port ? Number(location.port) : DEFAULT_PORTS[this.protocol()] || null;
},
protocol: function() {
return location.protocol.substr(0, location.protocol.length - 1);
},
replace: function() {
return this;
},
search: function(search, paramValue) {
if (search || paramValue) {
return this;
}
var query = {};
location.search.substr(1).split("&").forEach(function(pair) {
pair = pair.split("="); query[pair[0]] = decodeURIComponent(pair[1]);
});
return query;
},
url: function(url, replace) {
return this.path();
}
});
}]);

Resources