Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dynamic location of assets in Angular

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.

like image 855
David Avatar asked Jan 19 '18 18:01

David


2 Answers

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.

like image 162
vsoni Avatar answered Nov 17 '22 15:11

vsoni


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.

like image 44
Okan Aslankan Avatar answered Nov 17 '22 15:11

Okan Aslankan