66

I have the following code, which was working fine until I deployed to a test server:

$scope.getUserList = function (userName) {
    $http({
        method: "get",
        url: "GetUserList",
        params: { userName: userName }
    }).
        success(function (data) {
            $scope.users = data;
        }).
        error(function () {
            alert("Error getting users.");

The problem is that I deployed to a virtual directory, and the call below is attempting to hit GetUserList from the server root. This makes sense, and I know a number of ways to fix it.

What I would like to know is the right way to reference the service URL in a way that is portable and maintainable in Angular.

9 Answers 9

105
+50

I'd suggest using an HTML base tag in the head, and coding all paths relative to this. In ASP.NET, for example, you can get a reference to the base of the application, which may or may not be the root path of the site, so using a base tag helps. Bonus: it works for every other asset too.

You can have a base path like this:

<base href="/application_root/" />

...and then links like "foo/bar.html" will actually be /application_root/foo/bar.html.

Another approach I like to use is to put named links in the header. I will often have an API root in one location and a directive template root somewhere else. In the head, I'll then add some tags like this:

<link id="linkApiRoot" href="/application_root/api/"/>
<link id="linkTemplateRoot" href="/application_root/Content/Templates/"/>

... and then use $provide in the module to get the link href and expose it to services and directives like so:

angular.module("app.services", [])
    .config(["$provide", function ($provide) {
        $provide.value("apiRoot", $("#linkApiRoot").attr("href"));
    }]);

... and then inject it to a service like this:

angular.module("app.services").factory("myAdminSvc", ["apiRoot", function (apiRoot) {
    var apiAdminRoot = apiRoot + "admin/";
    ...

Just my opinion though. Do the least complex thing for your application.

Sign up to request clarification or add additional context in comments.

9 Comments

Maybe it is better to use $provide.constant instead of $provide.value? Constants are available also during configuration phase (for example during routing). What do you think?
But how do you deal with the testing localhost:####? Wouldn't it be better if we store the base url in Web.Config (so that we can transform when publishing) instead of in the <header> section?
You create a dependency on jQuery by using $provide.value("apiRoot", $("#linkApiRoot").attr("href")); Instead you could use plain javascript, spiced with some jqLite: var elem = angular.element(document.querySelector('#linkApiRoot')); $provide.value("apiRoot", elem.attr("href"));
Thanks for the tip, just had the same problem when switching from IIS Express to local IIS. But instead of hard coding the base url, I just inserted the default action. <base href="@Url.Action("Home", "Index")" /> And it works as expected!!
Update on previous comment: If the path in $http.get('/api/app/' + action) starts with a forward slash, as in this example, the base tag is ignored. Right way is: $http.get('api/app/' + action).
|
29

I would suggest defining a module that contains a global config that you can then pass around your application:

// Module specific configuration
angular.module('app.config')
  .value('app.config', {
    basePath: '/' // Set your base path here
  });

Then you can access this from anywhere within your application thanks to AngularJS dependency injection:

// Make sure your config is included in your module
angular.module('app', ['app.config']);

// Access your config e.g. in a controller
angular.module('app')
  .controller('TestCtrl', ['$scope','app.config', function($scope, config){

    // Use config base path to assemble url
    $scope.url = config.basePath + 'GetUserList';
    ...
  }]);

Whenever the base path changes (e.g. when you change to another server or host), you just need to change it in your global config and you're done.

6 Comments

I like this. One path is seldom enough; you may have template paths, API paths, etc.
Indeed, you can add as many properties as you like to your config and use them anywhere in your application.
@jvandemo While resolving the config dependency, how does angular know that config refers to app.config, same what I have always wondered how angular knows ng refers to angular, does it use the order of the parameters?
Exactly what I needed to glue my AngularJS app with Symfony2! Thanks for sharing!
This answer seems easy but in terms of re-usability is actually poor. In order for you to use your module in another application you'd also be required to use this app.config module as well.
|
13

I wasn't able to use <base> tag since my application was created in a popup dynamically from another origion. I was considering the others option in this thread to use something like a basePath variable and use it in every $http, ng-src, templateUrl etc

But that was a bit overhead, built a interceptor that change every url before a xhr is made

var app = angular.module("myApp", []);

app.config(["$httpProvider", function($httpProvider) {
    $httpProvider.interceptors.push('middleware');
}]);

app.factory('middleware', function() {
    return {
        request: function(config) {
            // need more controlling when there is more than 1 domain involved
            config.url = "//example.com/api/" + config.url
            return config;
        }
    };
});

app.controller("Ctrl", ["$http", function($http) {
    $http.get("books"); // actually requestUrl = http://example.com/api/books
}])

And html aswell

<div ng-include src="'view'">
    <!-- actually src = http://example.com/api/view -->
</div>

But i do recommend to use <base> tag instead unless you are using window.popup()

4 Comments

This is a great solution! BUT, when you fetch views via xhr it will use this provided url!
@ammt I know, that was my intentions
There any way you can use this middleware with only $http? but not with ng-include/template. Because I want to have an relative path for my api.
@MaicolBen you can prefix your api and validate the URL so that it starts with /api/.../ or something. Otherwise ngResorce might be the best option
4

Use the $location service - it will return your path, the hash, the server address.. Everything you need! Your call would be to $location.path()+"/GetUserList" or something similar.

See here: http://docs.angularjs.org/guide/dev_guide.services.$location

2 Comments

$location.path() always seems to be blank. I just inject it into my controller like this, or something else I need to do? var saController = function ($scope, $http, $location) {}
Sorry for late response, but in our app we configured $locationProvider to support html5Mode, and that seems to kicked it into gear. The code looks something like this: .config([ '$locationProvider', function($locationProvider) { $locationProvider.html5Mode({ enabled: true, requireBase: false }); } ])
3

I had a similar problem I solve it with the just one line of code in my MVC page

<base href="~/" />

1 Comment

This is the simplest and best fix so far. This solved my typescript directive templateUrl: issue. Before I was using back ticks and adding my html to template:
2

The accepted answer helped me. I'm using Angular served up my an MVC app. I took one extra step so that my baseUrl could be used within my angular controllers for web api calls or for accessing templates.

Using ng-init to set the baseUrl (from a server side populated value)

<html ng-app="app" ng-controller="AppController">
<head>
    <base href="{{baseUrl}}" ng-init="baseUrl = '@Model.BaseUrl'" />
</head>

Angular controller

    $scope.manageCustomerGroups = function () {
        openDialog($scope.baseUrl + 'content/templates/customerGroups.html');
    }

Comments

0

A simple way, I'm using ASP.NET Razor (Web MVC), so I get the Application path and make it as the base of app.

<head>

<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Title</title>

<meta name="description" content="">
@{ var appPath = Request.ApplicationPath.ToString();
    if (!appPath.EndsWith("/")) {
        appPath = appPath + "/";
    }
}
<base href="@appPath" />

Comments

0

I would just make all URL relative.

url: "../GetUserList",

instead

url: "GetUserList",

Hard coding for the, <base href="/application_root/" /> dose not sound good idea to me as you might need to change this environment to environment, and has dependency over virtual directory name.

Comments

-1

In the ng-init function pass a parameter that contains the value the virtual directory (int ASP.NET stored in Request.ApplicationPath).

<div ng-controller="ControllerName" ng-init="init('@Request.ApplicationPath')">

Inside the angular controller, use this value as prefix of URL in every http call. You can use this function to combine the paths

function combinePath(path1, path2) {
    if (path1 == null) {
        return path2;
    }
    var last = path1.slice(-1);
    var first = path2.charAt(0);

    if (last == '/' && first == '/') {
        path1 = path1.substring(0, path1.length - 1);
    }
    return path1 + path2;
}

1 Comment

Things are missing in this answer. What does the init() function do? How is it connected to the combinePath() function? And finally, how is it used in the service?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.