Angular.js: add $locationProvider.baseHref()

Created on 27 May 2013  Â·  18Comments  Â·  Source: angular/angular.js

Running an Angular app in a subdirectory is painful. All your routes, resources, locations, etc etc have to be manually prefixed with the application context. Example:

$routeProvider.when(root + 'path', {templateUrl: root +'path/file.html', controller: 'SomeCtrl'});

//and

$location.path(root + 'somepath');

//and

return $resource(root + 'some/resource/:id', {id: '@id'});

It's annoying and also error prone if your development env runs in a subdirectory (pretty common).

One current solution is to add a <base href="/myroot/"> tag to the document. This solves the problems from the Angular perspective but causes many issues of its own, especially for third-party libs/widgets that use anchors.

The ideal would be to make $location configurable with a base path, much like Backbone router's root config.

$locationProvider.baseHref = "/myroot/";

$location.path('/awesome'); // goes to /myroot/awesome

$routeProvider.when('/mypath', ...); //matched at /myroot/mypath

$resource('/some/resource'); //maps to /myroot/some/resource

This change would make running Angular in different contexts _much_ easier.

Most helpful comment

A workaround I found for this was to redefine the behaviour of the $browser.baseHref() function in the application module's run() function like so:

// override the $browser.baseHref() method so it returns "/" without needing to use a <base href="/"> element
// in the head of the document (which causes problems with SVG patterns amongst other things)
$browser.baseHref = function() { return "/" };

All 18 comments

:+1: yeap!

Would simplify my life and save us quite a few lines of code. +1

:+1:

You know you can add

<base href="/myroot/" target="_blank"/>

to <head> and it will be picked?

that reference was in error, please ignore

A workaround I found for this was to redefine the behaviour of the $browser.baseHref() function in the application module's run() function like so:

// override the $browser.baseHref() method so it returns "/" without needing to use a <base href="/"> element
// in the head of the document (which causes problems with SVG patterns amongst other things)
$browser.baseHref = function() { return "/" };

@greglockwood I like that solution for setting base href asssuming you can get angular loaded with a correct path first ;) I'm hoping to put that to use tomorrow!

Correct me if I'm wrong -- is this issue about making angular apps that don't need to be tweaked when their actual root path changes? I'm still really looking forward to some sort of solution to being able to deploy apps in subdirectories without modifying their code.

@stu-salsbury I don't believe you are right in saying that this issue is about having Angular apps working in subdirectories without modifying lines of code. This issue is about being able to specify in code what is currently pulled from the <base href="..."> tag in the <head> of the document, as that <base> tag introduces other, unwanted problems. Regardless of those other problems, though, if you want to deploy an Angular app to http://server/directory, you are going to need to change the "base URL", which is a code change either way (either updating the <base> tag in the HTML or, if this feature gets added, in the JS code).

One way I have come up with in the past to get around an application being able to be deployed in a subdirectory or not and still work is to determine the base URL from the full URL, since you can subtract any known relative path from the whole URL. You could combine that trick with either my workaround above, or if this feature gets added, with the official function call to have this kind of "deploy anywhere" functionality.

Let me illustrate what I meant above in more concrete terms. Let's say I have all my application routes starting with /app, and I want it to work if deployed at either http://localhost:9000/ or http://sub.domain.com/my/new/angular-app/ or anywhere. What I can do when I start up in my Angular app is examine the current full URL, subtract everything from the last /app onwards, and then subtract the server and port and be left with the correct base URL. Then I could combine that with my workaround above to override the $browser.baseHref() function so it returns that base URL. Then, as far as I know, Angular will correctly prepend that base URL to the URL for all requests and everything should just work. Note: I haven't tested this.

The html still needs to reference js, images etc with relative paths, and if the base href in the file is incorrect, then those references won't work.

Making the references be absolute makes putting apps in subdirs broken again.

So I think this can't be solved with something added to $locationProvider, since Angular starts sometime after the document is loaded.

Right?

@wmertens correct. For asset paths in HTML files (paths in CSS are ok, they're loaded relative to the CSS file) you'd still need to prefix your paths with the root if you have a variable root.

we dove into this yesterday with @geddski and we have a plan to implement ngAppRoot directive which will take in a string that represents url prefix that should be used internally for $location, $http and $route urls.

with this and some sort of serverside templating (signified by [[ ]] in example below) you'll be able to do this:

<html ng-app="myApp" ng-app-root="[[ appContext]]">
  <head>
    <script src="[[ appContext ]]/myApp.js"></script>
    <link rel="stylesheet" href="[[ appContext ]]/myApp.css">
  </head>
  <body>
    <a href="[[ appContext ]]/cats">
      <img src="[[ appContext ]]/cats.jpg">
    </a>
  </body>
</html>

All of your urls in javascript will then remain absolute-root urls (e.g. /cats).

Would ngAppBase not be more consistent and less confusing given we already have $rootScope and root element concepts?

Thought about base, but don't want to mislead people into thinking it
behaves like On Jun 28, 2013 7:41 AM, "Pete Bacon Darwin" notifications@github.com
wrote:

Would ngAppBase not be more consistent and less confusing given we
already have $rootScope and root element concepts?

—
Reply to this email directly or view it on GitHubhttps://github.com/angular/angular.js/issues/2805#issuecomment-20188585
.

closing in favor of #3102

correct. the resolution rules for base[href] and "root" are very different, so we want to make the difference clear.

for example if base[href]="/foo/bar" and url is "/baz" then the final url is still "/baz". however if root is "/foo/bar" then the final url is "/foo/bar/baz".

..So is this resolved?

For those of trying to make sense of this, it would be helpful if someone provided an example of what we are supposed to do. Currently it is clear as mud.

Thanks in advance

The problem I'm having is I need to give the users a "share" button, which should generate a full URL to one of my routes:

if the app is running in a subdirectory

https://example.com/my-angular-app-root/index.html#/my/route?foo=bar

or if the app has its own domain

https://example.com/index.html#/my/route?foo=bar

So to attempt to construct this dynamically, I try this:

var url = $location.protocol() + '://' + $location.host() + subpath +  '/my/route.html';

The problem is, how do I know if angular is running in a subpath? Where can I get the value for that from? It'd be nice to just be able to do something like

var url = $location.baseHref() + '#/my/route';

Then I could easily generate URLs for things like "share buttons".

For those who still search for a solution, you can use $window.location.pathname.
I don't know why '$location.path()' returns not the same as '$window.location.pathName()', but it works for me.

Was this page helpful?
0 / 5 - 0 ratings