Angular 2 Set APP_BASE_HREF with a value from a Promise / Observable
Asked Answered
S

4

6

I try to set the APP_BASE_HREF in the "CoreModule" with a value from a async rest call. I can't see how this is done, because the provide method needs to return a string.

for example:

@NgModule({
    imports: [
        ...
        HttpModule
    ],
    ...
    providers: [
        ...
        ...
        BackendRequestClass,
        { provide: APP_BASE_HREF, useFactory: () => () => return '/some/path', deps: [], multi: true }
    ],
});

but when I need the value from a webservice, I can't return the string. Any ideas how this could be done?

thx

Sestet answered 13/9, 2017 at 6:27 Comment(0)
S
7

I tried your solution. The problem is, that at the time

{ provide: APP_BASE_HREF, useFactory: (config) => config.appBaseHref, deps: [ConfigService] }

the config.appBaseHref is not set yet. When I debug the code I see, that the APP_INITIALIZER is executed after the provider from APP_BASE_HREF

That causes, that the BASE_HREF is not set.

Sestet answered 13/9, 2017 at 6:50 Comment(0)
R
4

We had the same challenge: Set the APP_BASE_HREF dynamically, based on a result from an async API call to allow routing of an app which can be accessed through different URLs.

While the solution presented by Günter looks sweet, it unfortunately did not work, at least with Angular 7. In our case, APP_BASE_HREF always took precedence over APP_INITIALIZER, so we couldn’t initialize the APP_BASE_HREF based on the resolved promise value returned from the initializer.

Instead, I implemented the API call to happen before the Angular bootstrapping and then injected the provider so that it’s available within the Angular context.

Adding to main.ts:

fetchConfig().then(config => {
  platformBrowserDynamic([ { provide: ConfigData, useValue: config } ])
    .bootstrapModule(AppModule)
    .catch(err => console.log(err));
});

We can then configure the APP_BASE_HREF in the providers within app.module.ts:

providers: [
  // …
  { 
    provide: APP_BASE_HREF, 
    useFactory: (config: ConfigData) => config.base, 
    deps: [ ConfigData ] 
  }
]

[edit 2019-08-21] Clarification per the comments: fetchConfig() is the API request which gets the configuration and returns a Promise. And ConfigData is not an Angular service, but just a simple class which gets instantiated in fetchConfig() and which gives me type safety when accessing it later in my application:

function fetchConfig (): Promise<ConfigData> {
  return fetch('/api/config')
    .then(response => response.json())
    .then(response => new ConfigData(response));
}
Reavis answered 23/7, 2019 at 14:53 Comment(8)
Rather than going "outside" Angular, you might be able to use the PLATFORM_INITIALIZER mechanism to fetch the config. This kicks in nice and early (certainly before APP_INITIALIZER). platformBrowserDynamic([{ provide: PLATFORM_INITIALIZER, useValue: fetchConfig, multi: true }]).bootstrapModule(AppModule)Pelerine
@GaryMcGill Thanks for the input -- I'll give it a try once I have some spare minutes!Reavis
@Reavis can I see the code for fetchConfig()? Where are you defining config in main.ts ?Syndetic
@Syndetic It's just an HTTP request which fetches the config and resolves to a promise.Reavis
@Reavis ok thanks. ConfigData is a Service? If so, how did you create it in main.ts?Syndetic
No, not a service. It's just a simple class.Reavis
@Reavis I see. So how does app.module.ts know about this class? Is it static?Syndetic
@GaryMcGill Again thanks for the suggestion from July. Actually I just had some spare time to give this a try. Unfortunately PLATFORM_INITIALIZER doesn’t seem to support “waiting” for a promise (which would be necessary to await fetchConfig()). Anything I’m missing or misunderstanding here?Reavis
T
2

You can use APP_INITIALIZER to get the path in advance and then use a dependency as shown in Angularjs2 - preload server configuration before the application starts

export function loadConfig(config: ConfigService) => () => config.load()

@NgModule({
    declarations: [AppComponent],
    imports: [BrowserModule,
        routes,
        FormsModule,
        HttpModule],
    providers: [
        ConfigService,
        BackendRequestClass,
        { provide: APP_INITIALIZER,
          useFactory: loadConfig,
          deps: [ConfigService], 
          multi: true },
        { provide: APP_BASE_HREF, useFactory: (config) => config.appBaseHref, deps: [ConfigService] }

    ],
    bootstrap: [AppComponent]
})
export class AppModule { }

The ConfigService can inject BackendRequestClass or Http and fetch the data and then make it available using its appBaseHref property (just an example how it could be done).

Tureen answered 13/9, 2017 at 6:33 Comment(2)
This looks like a good solution in theory, however, APP_BASE_HREF seems to get executed before APP_INITIALIZER (at least on newer Angular versions -- I'm using 7). Has this changed, or is there something I'm missing?Reavis
I haven't worked with Angular since a while and also don't know.Orthman
L
0

This is a fairly common problem. But she could not get enough votes for the Angular team to take her on: https://github.com/angular/angular/issues/25932

Tried many ways. But apparently the only solution is to change maint.ts. I slightly modified the approach that qqilihq suggested:

main.ts:

(function fetchConfig (): Promise<any> {
  return fetch('/apiTrendz/publicApi/info')
    .then(response => response.json())
    .then(response => {
      platformBrowserDynamic(
        [{provide: APP_BASE_HREF, 
         useValue: response.baseHrefFromBack ? '/' + response.baseHrefFromBack + '/' 

: '/default-base-url/'}]) .bootstrapModule(AppModule) .catch(err => { console.error(err) }); }); })();

Note that here we are bootstrapModule(AppModule), which was before in code by default, is now executed after the request is executed. And we will provide APP_BASE_HREF right here, so you won’t need to do anything in the module.

Also, please note that baseHref must be wrapped in slashes (/baseHref/), otherwise you can see a redirect to the main page later when reloading the page with other routes.

Ligure answered 15/6, 2023 at 16:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.