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

The Hungry Dictator
  • 3,444
  • 5
  • 37
  • 53
Kevin Clerc
  • 141
  • 1
  • 2
  • 8

4 Answers4

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.

Kevin Clerc
  • 141
  • 1
  • 2
  • 8
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));
}
qqilihq
  • 10,794
  • 7
  • 48
  • 89
  • 1
    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)` – Gary McGill Jul 24 '19 at 10:26
  • @GaryMcGill Thanks for the input -- I'll give it a try once I have some spare minutes! – qqilihq Jul 24 '19 at 22:14
  • @qqilihq can I see the code for fetchConfig()? Where are you defining config in main.ts ? – Arnold.Krumins Aug 16 '19 at 09:55
  • @Arnold.Krumins It's just an HTTP request which fetches the config and resolves to a promise. – qqilihq Aug 20 '19 at 12:49
  • @qqilihq ok thanks. ConfigData is a Service? If so, how did you create it in main.ts? – Arnold.Krumins Aug 21 '19 at 07:29
  • No, not a service. It's just a simple class. – qqilihq Aug 21 '19 at 10:30
  • @qqilihq I see. So how does app.module.ts know about this class? Is it static? – Arnold.Krumins Aug 21 '19 at 10:47
  • @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? – qqilihq Nov 08 '19 at 17:36
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).

Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • 1
    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? – qqilihq Jul 22 '19 at 13:36
  • I haven't worked with Angular since a while and also don't know. – Günter Zöchbauer Jul 22 '19 at 16:41
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.

sandroid
  • 311
  • 2
  • 5