Dynamic location of assets in Angular
Asked Answered
K

2

9

My app is going to be used on multiple machines, and it may be located in a different place each time. For example, the home route may be at any of the following locations:

localhost:9000/home

localhost:9000/abc/home

localhost:2000/yet/another/location/home

So I want to make sure my app will work no matter where it needs to find its assets. All my paths are relative, so hopefully that's a step in the right direction, like this:

<link rel="stylesheet" href="assets/global-styles.css">

and in my component templates, like this:

<img src="assets/components/dashboard/images/foo.png" />

Here's what I've tried so far:

I've modified my <base> attribute so manually grab the part of the URL after the port and before "home" like so:

<html>
    <head>
    <script>
        // manually sets the <base> tag's href attribute so the app can be located in places other than root
        var split = location.pathname.split('/');
        var base = "";
        for (var i = 0; i < split.length - 1; i++) {
            base += split[i];
            if (i < split.length - 2) {
                base += "/";
            }
        }
        window['_app_base'] = base;
        document.write("<base href='" + base + "' />");
    </script>
        ....

So far example, if the user loaded the home page at localhost:9000/hello/home, then the <base> tag would look like this:

<base href='/hello' />

That code also sets a variable to this same value, which I then use in my app.module.ts to provide a new base value there as well:

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

@NgModule({
    declarations: [...],
    imports: [...],
    providers: [
        ...,
        { provide: APP_BASE_HREF, useValue: window['_app_base'] || '/' }
    ]
});

However despite all this, the app is still looking for assets in the assets folder, which it thinks is still in the root location.

GET http://localhost:9000/assets/ionic-styles.css net::ERR_ABORTED
GET http://localhost:9000/inline.bundle.js net::ERR_ABORTED
GET http://localhost:9000/assets/font-awesome-4.6.3/css/font-awesome.min.css net::ERR_ABORTED
GET http://localhost:9000/assets/bootstrap.min.css net::ERR_ABORTED
GET http://localhost:9000/assets/global-styles.css net::ERR_ABORTED
GET http://localhost:9000/polyfills.bundle.js net::ERR_ABORTED
GET http://localhost:9000/styles.bundle.js net::ERR_ABORTED
GET http://localhost:9000/vendor.bundle.js net::ERR_ABORTED

What am I missing? I've also seen people take an approach where they specify a path in their ng build command... is that necessary on top of everything above? I'd really like to avoid having to make a separate build for everyone install, but if that's what it takes, please let me know.

Thanks.

UPDATE: After a frustrating few days, my app is working using the code shown in the question above. It turns out I only needed to have the <script> in my <head> to modify the <base>. There was no need to add the APP_BASE_HREF in the ngModule providers. However, adding the APP_BASE_HREF doesn't break anything either...

This is one of those problems where it "just worked" after dropping the problem for a week and coming back to it. Maybe there was some kind of cache issue going on? After all, I had tried updating the <base> last week, but it was still looking in the wrong place for the assets. Though I had cleared the browser cache multiple times last week as I tried to debug this issue, so I'm not sure if cache was actually the problem.

I'm sorry this answer isn't going to be helpful for people who face this issue in the future. I really have no idea what fixed the issue. My best guess is that one of the answers below will likely point you in the right direction. It seems like no matter what I tried, it wasn't working for some unknown reason.

If you're facing this problem and have a specific question, feel free to message me or leave a comment, and I'll tell you how I have things set up.

Knit answered 19/1, 2018 at 18:29 Comment(4)
The problem is precisely that your URLs are absolute, not relative. Use assets/global-styles.css, not /assets/global-styles.css.Glow
@JBNizet after changing the paths to no longer have a leading slash, I still get the exact same result.Knit
@Knit did you try './assets/you-assets-path/...' ?? also check this github.com/angular/angular-cli/wiki/…Benedikta
updated my question to include the eventual resolution... it seems to be "just working" now with the code shown above. I hate/love when that happens... though in this case, given the bounty and all the time spent on this problem (by me and others) I would have loved to actually understand what the issue was...Knit
B
5

You just have to make the following base href in your index.html

<base href="./">

which you can do either manually or while doing build like

ng build --base-href ./

You don't need to do anything else on the angular side apart from copying your contents from dist directory to your server's /whatever/is/your/root/ directory.

Now you can access your angular app with url

localhost:port/whatever/is/your/root/index.html

UPDATE

You probably may want to do couple of more things at your server. But, how you do, will entirely depend on what technology you use on your server.

  1. Make localhost:port/whatever/is/your/root/index.html as your default home page.

  2. To make your angular app's html5 route urls work properly on browser refresh, you should forward all the requests to url localhost:port/whatever/is/your/root/** to localhost:port/whatever/is/your/root/index.html. Note that you should forward the request and not send redirect.

Beset answered 24/1, 2018 at 11:40 Comment(2)
That works for most of my files, but doing that will completely destroy any CSS background-images you have.Mastersinger
@Eliezer Berlin - You can provide relative path like background-image: url(../../../assets/images/bg.jpg); . This should work fine.Beset
A
2

That is just how the url works in browsers. You can change them with a pipe. Considering your injected value works as intended this must work. Also it must be in strings like 'APP_BASE_HREF' in module.

 @Pipe({
  name: 'assetspipe'
})
export class AssetspipePipe implements PipeTransform {
      baseUrl: string;
      constructor(@Inject('APP_BASE_HREF') baseHref: string) {
        this.baseUrl = baseHref;
      } 

      transform(value: string) {
        const newval = this.baseUrl + value;
           return newval;
      } 

}

then in your component:

<img src="{{'assets/components/dashboard/images/foo.png' | assetspipe}}"/>

the pipe should add the baseurl as prefix.

Also check this thread. Your implementation may have a problem about loading bundles.

Arrowhead answered 22/1, 2018 at 16:59 Comment(4)
shouldn't that pipe not be necessary as long as the paths in the HTML are all relative to the base tag's correct path?Knit
I'am not sure that I understand your problem. ng serve --base-href /deployUrl/ should serve the app in localhost:xxx/deployurl and update base-href tag in index.html which is deployed. you dont need anything else. Cli does not build the application everytime the index.html(your root component) being hit.Arrowhead
I can't use ng serve. I'm running this off a non-Node back-end server. Would updating the ng build params to a different outDir do the trick?Knit
I dont know your project structure, so I cannot tell anything about that but you can try it.Arrowhead

© 2022 - 2024 — McMap. All rights reserved.