Using Angular's Base HREF in Paths

August 7, 2021
No Comments.

I was recently working with a client and they were having an odd problem with Angular. They’d build their Angular apps in isolation then move them into an ASP.NET Core project and their asset links would break. Let’s look at why this happens and how to address it.

The problem comes down to this simple idea. So the HTML looked like this:

<div>
  <img src="/assets/img/comingsoon.jpg" 
       alt="logo" 
       class="logo">
</div>

This works when running the project directly via ‘ng serve’ because the file is in the assets folder:

Project

Project Path to /assets/img/comingsoon.jpg

When deploying the app to an ASP.NET Core project, it works…as soon as it’s being deployed to the root of project. The problem is that the Angular app just renders HTML for the browser to load the images. So the absolute URL (e.g. starting with a slash) assumes that it is going to be deployed to the root of the resulting webserver. But what if it’s not deployed that way. A common case is when it’s deployed to IIS as a website in a folder of a site. For example:

A nested app in IIS

In ASP.NET’s Razor handles this by using the tilde syntax:

<img src="~/img/2019/keynote-speaker.jpg" 
     alt-="Speaker Name" 
     class="img-responsive img-circle col-md-3" />

The tilde (~) tells ASP.NET Core to use the root of the app (whether that be the root of the site, or the App root in IIS). For Angular, we needed a different approach. Luckily, Angular already needed to know this for their own needs.

In Angular, they use a header value (base) to specify where this app is hosted:

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>NgRootdir</title>
  <base href="/">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
  <app-root></app-root>
</body>
</html>

The base element’s href tells Angular how to deal with URLs but it doesn’t extend to HTML markup. In order to use this, we have to do some work. I found this out through a StackOverflow question answered by Ian Campbell:

The trick is to create a function for returning the Base HREF from PlatformLocation:

export function getBaseHref(platformLocation: PlatformLocation): string {
  return platformLocation.getBaseHrefFromDOM();
}

You can then use this to allow the value to be injected into components by creating a Provider:

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule
  ],
  providers: [
    {
      provide: APP_BASE_HREF,
      useFactory: getBaseHref,
      deps: [PlatformLocation]
    }
  ],
  bootstrap: [AppComponent]
})

Once this is done, you can just inject the APP_BASE_HREF into your components via the @Inject decorator:

export class AppComponent {
  title = 'ng-rootdir';
  
  constructor(@Inject(APP_BASE_HREF) public baseHref:string) {
  }
}

With that baseHref in your class, you can use it in the markup:

<div>
  <img [src]="baseHref + '/assets/img/comingsoon.jpg'" 
       alt="logo" 
       class="logo">
</div>

By adding the baseHref to the start of the image URL, it can work in both scenarios. With this in place you can use the Base HREF in other places as well.

Interested?