Angular 2+ - Set base href dynamically

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.


Solution 1:

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.

Solution 2:

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 { }

Solution 3:

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