173

We have an enterprise app that uses Angular 2 for the client. Each of our customers has their own unique url, ex: https://our.app.com/customer-one and https://our.app.com/customer-two. Currently we are able to set the <base href...> dynamically using document.location. So user hits https://our.app.com/customer-two and <base href...> gets set to /customer-two...perfect!

The problem is if the user is for example at https://our.app.com/customer-two/another-page and they refresh the page or try to hit that url directly, the <base href...> gets set to /customer-two/another-page and the resources can't be found.

We've tried the following to no avail:

<!doctype html>
<html>

<head>
  <script type='text/javascript'>
    var base = document.location.pathname.split('/')[1];
    document.write('<base href="/' + base + '" />');
  </script>

...

Unfortunately we are fresh out of ideas. Any help would be amazing.

Zze
  • 18,229
  • 13
  • 85
  • 118
sharpmachine
  • 3,783
  • 6
  • 19
  • 24
  • 1
    Have you attempted anything with [APP_BASE_HREF](https://angular.io/docs/ts/latest/api/common/index/APP_BASE_HREF-let.html)? I am not quite sure how it would be implemented, but it seems to be something that would be useful... – Jarod Moser Jun 30 '16 at 02:50
  • Of course it definitely depends on which version of the router you are currently using as well. Are you on the router version 3.0.0-alpha.x? – Jarod Moser Jun 30 '16 at 02:59
  • I'm using "@angular/router": "3.0.0-alpha.7" – sharpmachine Jun 30 '16 at 03:02
  • Hmmm.... I have been trying to understand the router and the best practices as well... Perhaps you should not set the base href dynamically and instead use [children routes](https://angular.io/docs/ts/latest/guide/router.html)? – Jarod Moser Jun 30 '16 at 03:54
  • I would like to do the same thing. Anymore ideas ? – Tom Aug 21 '16 at 00:20
  • 13
    Angular 7 - November 2018. this is still an issue. current answers provide a partial solution. doing stuff inside angular module is too late. as your index HTML is served it needs to know where to pull the scripts from. there are only 2 ways the way I see it - using asp\php\whatever to serve index.html with dynamic basehref content. or using native javascript right on the index.html file to change DOM content before it gets to angular. both are poor solutions to a common problem. sad. – Stavm Nov 08 '18 at 14:16
  • 1
    Can someone please answer my query also. https://stackoverflow.com/questions/53757931/angular-5-dynamic-base-reference-is-causing-duplicate-loading-of-bundleschunk – Karan Jan 02 '19 at 11:01
  • I ran into this same problem and specially using `@angular/pwa`, my approach was to make the server "Virtualize" ng app distribution, I detect the base URL for the customer and that it's requiring an angular App, then while responding `index.html` or `ngsw.json` I replace their content with the backend. It's fugly as shit, but it works. – Felype May 10 '19 at 12:24
  • Angular 12 - September 2021, any new solutions with the latest angular version? – ael Sep 05 '21 at 14:38

16 Answers16

211

The marked answer here did not solve my issue, possibly different angular versions. I was able to achieve the desired outcome with the following angular cli command in terminal / shell:

ng build --base-href /myUrl/

ng build --bh /myUrl/ or ng build --prod --bh /myUrl/

This changes the <base href="/"> to <base href="/myUrl/"> in the built version only which was perfect for our change in environment between development and production. The best part was no code base requires changing using this method.


To summarise, leave your index.html base href as: <base href="/"> then run ng build --bh ./ with angular cli to make it a relative path, or replace the ./ with whatever you require.

Update:

As the example above shows how to do it from command line, here is how to add it to your angular.json configuration file.

This will be used for all ng serving for local development

"architect": {
    "build": {
      "builder": "@angular-devkit/build-angular:browser",
      "options": {
        "baseHref": "/testurl/",

This is the config specific for configuration builds such as prod:

"configurations": {
   "Prod": {
     "fileReplacements": [
        {
          "replace": src/environments/environment.ts",
          "with": src/environments/environment.prod.ts"
        }
      ],
      "baseHref": "./productionurl/",

The official angular-cli documentation referring to usage.

Zze
  • 18,229
  • 13
  • 85
  • 118
  • 70
    This solution requires a build per customer, which is probably not solving the OP's problem. – Maxime Morin Jun 28 '17 at 14:53
  • 1
    @MaximeMorin in my experience so far doing this, `./` works for all locations. So actually I don't think you have to build per client. – Zze Jun 28 '17 at 21:31
  • 14
    @Zze My experience with `./` has been very different. It works until the user navigates to a route and hit the browser's refresh button or keys. At that point, our rewrites handle the call, serves the index page, but the browser assumes that `./` is the current route not the web app's root folder. We solved the OP's issue with a dynamic (.aspx, .php, .jsp, etc) index which sets the base href. – Maxime Morin Jun 28 '17 at 22:11
  • 4
    Using `--prod` during the build will not change your `base href` value, you shouldn't talk about it here. `--prod` tells `angular-cli` to use the `environment.prod.ts` file instead of the default file `environment.ts` in your `environments` folder – Mattew Eon Jan 11 '18 at 21:24
  • 1
    @MattewEon It was just demonstrating that it was compatible with the `--prod` flag since they were talking about deployment in the OP. – Zze Jan 11 '18 at 21:40
  • 1
    @Zze Oh okay. To my mind it's surely compatible because they do different actions, I just wanted to be sure you and other will understand the meaning of `--prod` flag – Mattew Eon Jan 11 '18 at 21:44
  • 2
    **Angular 7.1: `Unknown option: '--bh'`**. `--base-href` works. – Mir-Ismaili Jan 13 '19 at 17:58
  • Docs are here now if anyone is looking for more details (prob not more details yet just official...) https://angular.io/cli/build – Jessy Feb 17 '19 at 02:58
74

Here's what we ended up doing.

Add this to index.html. It should be the first thing in the <head> section

<base href="/">
<script>
  (function() {
    window['_app_base'] = '/' + window.location.pathname.split('/')[1];
  })();
</script>

Then in the app.module.ts file, add { provide: APP_BASE_HREF, useValue: window['_app_base'] || '/' } to the list of providers, like so:

import { NgModule, enableProdMode, provide } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { APP_BASE_HREF, Location } from '@angular/common';


import { AppComponent, routing, appRoutingProviders, environment } from './';

if (environment.production) {
  enableProdMode();
}

@NgModule({
  declarations: [AppComponent],
  imports: [
    BrowserModule,
    HttpModule,
    routing],
  bootstrap: [AppComponent],
  providers: [
    appRoutingProviders,
    { provide: APP_BASE_HREF, useValue: window['_app_base'] || '/' },
  ]
})
export class AppModule { }
sharpmachine
  • 3,783
  • 6
  • 19
  • 24
  • 1
    Error TS7017: Element implicitly has an 'any' type because type 'Window' has no index signature. `useValue: (window as any) ['_app_base'] || '/'`helps –  Dec 19 '16 at 17:08
  • Does not really work for the latest ng2, is there a solution? (loads bad scripts path, then good scripts path, but not on mobile) – Amit Dec 30 '16 at 08:26
  • Based on your answer, I was able to further refactor it a little, so that we don't have to hack out a function in `index.html`. – Krishnan Jan 16 '17 at 10:29
  • 1
    Adding it as an answer below, as the content length is too long for a comment. – Krishnan Jan 16 '17 at 10:35
  • 1
    @Amit Please see my answer below for an easy solve in the newest ng2. – Zze Feb 07 '17 at 21:19
  • 12
    How did you solve issue with other files. My browser is trying to load http://localhost:8080/main.bundle.js, but it can't because my contextPath is different? (It should be fetching http://localhost:8080/hello/main.bundle.js.) – Sasa Sep 02 '17 at 22:42
  • Can you please help me in answering my qstn also. https://stackoverflow.com/questions/53757931/angular-5-dynamic-base-reference-is-causing-duplicate-loading-of-bundleschunk – Karan Jan 02 '19 at 10:58
  • 1
    This works in angular 10. But make sure to remove line from index.html and remove setting base href in angular.json file. – Sameera K Apr 11 '23 at 11:16
42

Based on your (@sharpmachine) answer, I was able to further refactor it a little, so that we don't have to hack out a function in index.html.

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { APP_BASE_HREF, Location } from '@angular/common';

import { AppComponent } from './';
import { getBaseLocation } from './shared/common-functions.util';

@NgModule({
  declarations: [AppComponent],
  imports: [
    BrowserModule,
    HttpModule,
  ],
  bootstrap: [AppComponent],
  providers: [
    appRoutingProviders,
    {
        provide: APP_BASE_HREF,
        useFactory: getBaseLocation
    },
  ]
})
export class AppModule { }

And, ./shared/common-functions.util.ts contains:

export function getBaseLocation() {
    let paths: string[] = location.pathname.split('/').splice(1, 1);
    let basePath: string = (paths && paths[0]) || 'my-account'; // Default: my-account
    return '/' + basePath;
}
Krishnan
  • 1,464
  • 15
  • 21
  • 1
    I have myapp deployed to subfolder. My url now looks like this: `http://localhost:8080/subfolder/myapp/#/subfolder/myroute` Is this how yours look like? Do you know how to get rid of the subfolder after the # so that it can just be `http://localhost:8080/subfolder/myapp/#/myroute`? – techguy2000 Apr 05 '17 at 18:37
  • Oh. I think href base is only required for LocationStrategy. In my case, I am using HashLocationStrategy. So I con't even need to set it. Did you have write special code to handle refresh with LocationStrategy? – techguy2000 Apr 05 '17 at 19:03
  • 1
    Yes, `base href` is only required for `LocationStrategy` AFAIK. As for writing special code to handle refresh with LocationStrategy, not sure what you mean exactly. Are you asking, what if my "base href" changes during an activity in my app? Then, yes, I had to do a dirty thing like `window.location.href = '/' + newBaseHref;` where it was required to be changed. I am not too proud of it. Also, to give an update, I moved away from these hack solutions in my app as it was becoming a design problem for me. Router params work perfectly fine, so I stuck to it. – Krishnan Apr 06 '17 at 06:34
  • 1
    What does your `appRoutingProvider` look like, Krishnan? I see the accepted answer used the same; maybe you just copied his `providers`, but if not, can you give me a sample or describe its purpose? The [documentation only **`imports`** routing modules](https://angular.io/docs/ts/latest/guide/router.html), it doesn't use any routing-related providers (as far as I can see) – Nate Anderson Apr 06 '17 at 20:15
  • @Krishnan Here is what I have to do with nginx to make it work: `location /subfolder/myapp/ { try_files $uri /subfolder/myapp/index.html; } location /subfolder2/myapp/ { try_files $uri /subfolder2/myapp/index.html; }` – techguy2000 Apr 07 '17 at 06:55
  • @TheRedPea yes, sorry, I just copied the accepted answer's snippet. My main purpose was only to show how to use `useFactory` option for providers to keep the code clean, that's all. I don't think any of those imports matter or are related to the context of this question. Maybe, better I edit my snippet, to avoid any confusion. @techguy2000 sorry, I have no idea about nginx. If it worked for you then great, and good to know too! – Krishnan Apr 07 '17 at 09:07
  • Nice dynamic solution but I'm struggling with calling app via `http://my.app.com/app1` (no / at the end). Using hardcoded base-href and calling `http://my.app.com/app1` changes url automatically to `http://my.app.com/app1/` – xforfun Apr 27 '21 at 11:56
  • @xforfun I suspect that the trailing slash is added not by the Angular app but your *web server* when it is redirecting the request to the appropriate website. Curious to know if that is the case. – Krishnan May 05 '21 at 09:48
  • This works in angular 10. But make sure to remove line from index.html and remove setting base href in angular.json file. – Sameera K Apr 11 '23 at 11:16
25

I use the current working directory ./ when building several apps off the same domain:

<base href="./">

On a side note, I use .htaccess to assist with my routing on page reload:

RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule .* index.html [L]
Daerik
  • 4,167
  • 2
  • 20
  • 33
  • 2
    Thank you so much for the `.htaccess` file – Shahriar Siraj Snigdho Mar 27 '18 at 10:04
  • 13
    This answer is incorrect, it does not work for routes such as /your-app/a/b/c. If you refresh your page from such a route, Angular will look for the runtime, polyfills and main JS files and styles CSS file under /your-app/a/b/. They are obviously not at that location, but under /your-app/ – toongeorges May 09 '18 at 17:19
  • 2
    @toongeorges This answer was written almost two years ago using a configuration file that is detected and executed by the Apache Web Server software to handle routing on page reload. For you to say this solution is incorrect and does not work is misleading, but what you mean is that you could not figure out how to get it to work with your configuration. – Daerik May 09 '18 at 18:15
  • I overlooked the Apache rewrite rule and I do not know enough about it to say that this would not work on Apache, so I may be wrong and your answer may be correct. Though in any case I prefer a solution that does not require you to modify the server configuration as there have been given multiple answers here, because this makes the application portable across different servers. – toongeorges May 09 '18 at 18:41
  • 1
    Using `./` breaks routing to addresses which are multiple levels deep. e.g. `http://localhost:4200/foo/bar`. – bvdb Jan 17 '21 at 01:50
20

Simplification of the existing answer by @sharpmachine.

import { APP_BASE_HREF } from '@angular/common';
import { NgModule } from '@angular/core';

@NgModule({
    providers: [
        {
            provide: APP_BASE_HREF,
            useValue: '/' + (window.location.pathname.split('/')[1] || '')
        }
    ]
})

export class AppModule { }

You do not have to specify a base tag in your index.html, if you are providing value for APP_BASE_HREF opaque token.

Community
  • 1
  • 1
Karanvir Kang
  • 2,179
  • 19
  • 16
14

This work fine for me in prod environment

<base href="/" id="baseHref">
<script>
  (function() {
    document.getElementById('baseHref').href = '/' + window.location.pathname.split('/')[1] + "/";
  })();
</script>
Avikar
  • 339
  • 3
  • 4
  • 2
    This solution has a great limitation with multiple VD setup in IIS. It causes the duplicate loading of chunks. – Karan Jan 02 '19 at 12:27
  • Using hardcoded base-href and calling `http://my.app.com/app1` changes url to `http://my.app.com/app1/` ( / at the end) This solution also worked for me fine in case of `http://my.app.com/app1` since in changes to `http://my.app.com/app1/` - otherwise resources cannot be loaded. – xforfun Apr 27 '21 at 11:53
8

In angular4, you can also configure the baseHref in .angular-cli.json apps.

in .angular-cli.json

{   ....
 "apps": [
        {
            "name": "myapp1"
            "baseHref" : "MY_APP_BASE_HREF_1"
         },
        {
            "name": "myapp2"
            "baseHref" : "MY_APP_BASE_HREF_2"
         },
    ],
}

this will update base href in index.html to MY_APP_BASE_HREF_1

ng build --app myapp1
Vince
  • 620
  • 1
  • 5
  • 9
5

If you are using Angular CLI 6, you can use the deployUrl/baseHref options in angular.json (projects > xxx > architect > build > configurations > production). In this way, you can easily specify the baseUrl per project.

Check that your settings are correct by looking at the index.html of the built app.

Katlu
  • 496
  • 6
  • 14
5

Had a similar problem, actually I ended up in probing the existence of some file within my web.

probePath would be the relative URL to the file you want to check for (kind of a marker if you're now at the correct location), e.g. 'assets/images/thisImageAlwaysExists.png'

<script type='text/javascript'>
  function configExists(url) {
    var req = new XMLHttpRequest();
    req.open('GET', url, false); 
    req.send();
    return req.status==200;
  }

  var probePath = '...(some file that must exist)...';
  var origin = document.location.origin;
  var pathSegments = document.location.pathname.split('/');

  var basePath = '/'
  var configFound = false;
  for (var i = 0; i < pathSegments.length; i++) {
    var segment = pathSegments[i];
    if (segment.length > 0) {
      basePath = basePath + segment + '/';
    }
    var fullPath = origin + basePath + probePath;
    configFound = configExists(fullPath);
    if (configFound) {
      break;
    }
  }

  document.write("<base href='" + (configFound ? basePath : '/') + "' />");
</script>
Ronald Korze
  • 734
  • 5
  • 16
  • This solution was the only one really working for my deployment scenario: fully dynamic base path and PathLocationStrategy. All the other solutions would not work on hard reload on sub-paths. – aPokeIntheEye May 02 '22 at 19:54
3

Add-ons:If you want for eg: /users as application base for router and /public as base for assets use

$ ng build -prod --base-href /users --deploy-url /public
3rdi
  • 475
  • 4
  • 12
2

You can set the base href dynamically in the app.module.ts file using this: https://angular.io/api/common/APP_BASE_HREF

import {Component, NgModule} from '@angular/core';
import {APP_BASE_HREF} from '@angular/common';

@NgModule({
  providers: [{provide: APP_BASE_HREF, useValue: '/my/app'}]
})
class AppModule {}
Nick Gallimore
  • 1,222
  • 13
  • 31
  • This does not have the same exact effect as setting ``. See https://stackoverflow.com/questions/69094451/what-is-the-difference-between-html-base-href-and-angular-app-base-href – RocketMan Jan 18 '23 at 01:45
0

In package.json set flag --base-href to relative path:

"script": {
    "build": "ng build --base-href ./"
}
0

I am late for the show, but you can use a rewrite rule if you are deploying to IIS (I am sure it can be done with other webservers as well).

You can modify the content of the outgoing response when the index.html is requested and replace the base href dynamically. (url rewrite module must be installed into IIS)

The following example assumes you have <base href="/angularapp/"> in your index.html

<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <system.webServer>
        <rewrite>
            <outboundRules>
                <rule name="Modify base href to current path">
                    <match filterByTags="Base" pattern="^(.*)(/angularapp/)(.*)" />
                    <conditions logicalGrouping="MatchAll" trackAllCaptures="true">
                        <add input="{REQUEST_URI}" pattern="^(.*)/index.html$"/>
                    </conditions>
                    <action type="Rewrite" value="{C:1}/" />
                </rule>
            </outboundRules>
        </rewrite>
    </system.webServer>
</configuration>
morcibacsi
  • 43
  • 7
0

This has been a massive headache, #location strategy doesnt seem to do what is says on the tin (seemed like it was an obvios fix but sadly not) so here is what worked for me. Thanks to all for the pointers!

Note I am calling php pages from within my app that are relative to the application path, this was going wrong on a refresh because the folder was one level too high. This is addressed in the in2 section of the config below.

Create a web.config file and put it in your /src folder

<configuration>
<system.webServer>
  <rewrite>

<rules>
            <clear />
            <rule name="in2" stopProcessing="true">
                <match url="(.*)(.php)" />
                  <conditions logicalGrouping="MatchAll" trackAllCaptures="false">
                    <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
                    <add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
                </conditions>
                <action type="Rewrite" url="../{R:0}" />
            </rule>
            <rule name="Angular Routes" stopProcessing="true">
                <match url=".*" />
                <conditions logicalGrouping="MatchAll" trackAllCaptures="false">
                    <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
                    <add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
                </conditions>
                <action type="Rewrite" url="../myappname/" />
            </rule>

</rules>
  </rewrite>
</system.webServer>

Create a myappname.html file and put it in the /src folder Add both to the Angular.json build section under assets

"assets":{ ["projects/myapp/src/myappname.html","projects/toolbar2/src/web.config"
]

In the index.html

 <script type='text/javascript'>
function configExists(url) {
  var req = new XMLHttpRequest();
  req.open('GET', url, false); 
  req.send();
  return req.status==200;
}

var probePath = 'myappname.html';
var origin = document.location.origin;
var pathSegments = document.location.pathname.split('/');

var basePath = '/'
var configFound = false;
for (var i = 0; i < pathSegments.length; i++) {
  var segment = pathSegments[i];
  if (segment.length > 0) {
    basePath = basePath + segment + '/';
  }
  var fullPath = origin + basePath + probePath;
  configFound = configExists(fullPath);
  if (configFound) {
    break;
  }
}

document.write("<base href='" + (configFound ? basePath : '/') + "' />");
Richard Turner
  • 423
  • 4
  • 12
-1

Frankly speaking, your case works fine for me!

I'm using Angular 5.

<script type='text/javascript'>
    var base = window.location.href.substring(0, window.location.href.toLowerCase().indexOf('index.aspx'))
    document.write('<base href="' + base + '" />');
</script>
Aviw
  • 1,056
  • 11
  • 14
  • Does this solution worked for you ? I am facing the problem. Can you please reply to my post as well. https://stackoverflow.com/questions/53757931/angular-5-dynamic-base-reference-is-causing-duplicate-loading-of-bundleschunk – Karan Jan 03 '19 at 11:50
  • 1
    Please avoid using document.write(): https://developers.google.com/web/updates/2016/08/removing-document-write – Patrick Hillert Aug 19 '19 at 13:03
-15

I just changed:

  <base href="/">

to this:

  <base href="/something.html/">

don't forget ending with /

Jason Aller
  • 3,541
  • 28
  • 38
  • 38
Huy - Logarit
  • 634
  • 8
  • 13