15

I am working with Angular2 RC5. I would like to be able to load my server IP address and some other configuration parameters before my app boots up. How can I do this in the latest RC5?

I have seen other articles, but they are not helpful:

  1. How to preload a config file in angular2: The answer provided here is specific to webpack. I am using systemjs.
  2. https://medium.com/@hasan.hameed/reading-configuration-files-in-angular-2-9d18b7a6aa4#.m8lontnas: This is for the older version of Angular2. Some of the classes used are deprecated.

Any help would be greatly appreciated.

Edit

I tried to use the APP_INITIALIZER as follows in my app.module.ts:

    import { NgModule, provide, APP_INITIALIZER }       from "@angular/core";
    import { ConfigService } from "./shared/services/config.service";
    @NgModule({
        declarations: [AppComponent],
        imports: [BrowserModule,
            routes,
            FormsModule,
            HttpModule],
        providers: [AuthService,
            Title,
            appRoutingProviders,
            ConfigService],
        bootstrap: [AppComponent
            , provide(APP_INITIALIZER,
                {
                    useFactory: (config: ConfigService) => () => config.load(),
                    deps: [ConfigService], multi: true
                })]
    })
    export class AppModule { }

This is my config.service.ts file:

    import { Config } from "../model/config";

    export class ConfigService {
        constructor() {
        }

        load(): Promise<Config> {
            let config: Config = new Config("localhost", "5050");
            return Promise.resolve(config);
        }
    }

Note that I will eventually re-write load to actually read a property or some such file to load configuration.

This is what config.ts looks like:

export class Config {

    constructor(
        private machine: string,
        private port: string
    ) {
    }

    private base_url: string = "http://" + this.machine +
    this.port + "/";

    public loginURL: string = this.base_url + "login";
}

I am getting a compile error

public/app/app.module.ts(27,11): error TS2345: Argument of type '{ declarations: (typeof AppComponent ...' is not assignable to parameter of type 'NgModuleMetadataType'.
  Types of property 'bootstrap' are incompatible.
    Type '(typeof AppComponent | Provider)[]' is not assignable to type '(Type | any[])[]'.
      Type 'typeof AppComponent | Provider' is not assignable to type 'Type | any[]'.
        Type 'Provider' is not assignable to type 'Type | any[]'.
          Type 'Provider' is not assignable to type 'any[]'.
            Property '[Symbol.iterator]' is missing in type 'Provider'.

Edit 2

I move on to the next step. Update ConfigService to read a json file using http.get. Now I get an error. Here are the updated files:

export class ConfigService {
    private config: Config;
        constructor(private http: Http) {
    }

    load(): Promise<Config> {
            this.http.get("/blah/config.json")
                 .map(res => res.json())
                 .subscribe((env_data) => {
                     console.log(env_data);
                  });
        this.config = new Config("localhost", "5050");
        return Promise.resolve(this.config);
    }

    getConfig(): Config {
        return this.config;
    }
}

Here is the providers section in NgModule class

    providers: [AuthService,
    Title,
    appRoutingProviders,
    ConfigService,
    {
        provide: APP_INITIALIZER,
        useFactory: (config: ConfigService) => () => config.load(),
        deps: [ConfigService, Http],
        multi: true
    }],

Note that in RC6 I am not able to import HTTP_PROVIDERS since I get the error that these are not defined in the http module anymore. This is why I tried Http.

On runtime, I get following error

(index):27 Error: Error: Can't resolve all parameters for ConfigService: (?).(…)(anonymous function) @ (index):27
ZoneDelegate.invoke @ zone.js:332
Zone.run @ zone.js:225(anonymous function) @ zone.js:591
ZoneDelegate.invokeTask @ zone.js:365
Zone.runTask @ zone.js:265
drainMicroTaskQueue @ zone.js:497
ZoneTask.invoke @ zone.js:437
Community
  • 1
  • 1
Rashmi Dixit
  • 162
  • 1
  • 2
  • 8

1 Answers1

33

Provide this in your root module

{provide: APP_INITIALIZER, useValue: () => promise, multi: true}]}

where () => promise is some call that returns a Promise that resolves to the desired value.

See also How to pass parameters rendered from backend to angular2 bootstrap method

You can pass a service as dependency where you can store the result.

update

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

@NgModule({
        declarations: [AppComponent],
        imports: [BrowserModule,
            routes,
            FormsModule,
            HttpModule],
        providers: [AuthService,
            Title,
            appRoutingProviders,
            ConfigService,
            { provide: APP_INITIALIZER,
              useFactory: loadConfig,
              deps: [ConfigService], 
              multi: true }
        ],
        bootstrap: [AppComponent]
    })
    export class AppModule { }

update 2

Plunker example

Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • I updated my answer. `provide()` is deprecated, I changed it to the new object literal form. In `@NgModule()` providers need to go to `providers: [...]`, the parameter after `bootstrap` was the way before `@NgModul()` existed. – Günter Zöchbauer Sep 02 '16 at 03:51
  • The `ConfigService` does have the `@Injectable()` decorator? – Günter Zöchbauer Sep 07 '16 at 05:47
  • I see, thanks for the feedback :) – Günter Zöchbauer Sep 07 '16 at 08:22
  • 1
    Hello. I have provided `ConfigService` as you suggest in my root `NgModule` but when I debug in the browser `config.load()` it always returns undefinded. – snaphuman Sep 19 '16 at 01:12
  • Can't tell from this information. Seema to work for others. I'd suggest to create a new question with more details. – Günter Zöchbauer Sep 19 '16 at 03:10
  • How do I inject my config in order to use the values in a component? I keep getting a "No provider for Config" error – Francium123 Sep 30 '16 at 11:00
  • I assume `providers: [...]` doesn't contain `Config`. If not please create a new question with your code. Hard to tell without seeing your code. – Günter Zöchbauer Sep 30 '16 at 11:02
  • 5
    When using AOT, useFactory should not get a dynamic function, but rather exported function. See http://stackoverflow.com/a/41636880/499839 – Mika Vatanen Feb 11 '17 at 11:43
  • 1
    @Mika thanks for the hint! AoT wasn't really a thing back then when I posted the answer. Updated my answer. – Günter Zöchbauer Feb 11 '17 at 11:45
  • 1
    The problem is that promise inside config.load() method is resolved only after initialization. Do you have any idea how to manage this? – magos Aug 02 '17 at 13:49
  • That shouldn't be the case. Are you sure you're returning the `Promise` or `Observable` so that Angular can wait for it to complete? – Günter Zöchbauer Aug 02 '17 at 13:51
  • No, I was returning Function instead Promise. Now when I changed returned value for a Promise I'm getting error: – magos Aug 02 '17 at 14:21
  • appInits[i] is not a function ; Zone: ; Task: Promise.then ; Value: TypeError: appInits[i] is not a function – magos Aug 02 '17 at 14:21
  • You can see plunker here https://plnkr.co/edit/P8iLISfjClIcwpAzsUsk?p=preview Here is my stackoverflow question. You can answer there https://stackoverflow.com/questions/45462636/how-to-make-a-request-before-angular-4-application-bootstrap?noredirect=1#comment77886373_45462636 – magos Aug 02 '17 at 14:25
  • You are using `useFactory`, not `useValue` like my example does. Your factory returns a `Promise`, my example returns a function that returns a Promise (or Observable) (`useValue: () => ...` – Günter Zöchbauer Aug 02 '17 at 14:27
  • As I can see in updated part of your answer you use useFactory too. – magos Aug 02 '17 at 14:30
  • I see. It might work as well, but it definitely needs to return a function, not a `Promise` – Günter Zöchbauer Aug 02 '17 at 14:34
  • Okay, thanks. For now I've changed it to return a function (updated plunker), but I'm getting Cannot read property 'load' of undefined ; Zone: ; Task: Promise.then ; Value: TypeError: Cannot read property 'load' of undefined Despite of the fact I added StartupService to providers array. – magos Aug 02 '17 at 14:37
  • Do you have an idea what is going on - why my startupService is undefined? It seems deps was not injected, but why. – magos Aug 02 '17 at 14:41
  • It would be much easier if you'd create a runnable Plunker that allows to reproduce. You only need to click the `new` button and then select `Angular` to get a prepared template. – Günter Zöchbauer Aug 02 '17 at 14:43
  • Ok, here you go https://plnkr.co/edit/xaUydbigJ4oqHPK9wRpZ It does not work because of the same reason it doesn't work in my application (same error) – magos Aug 02 '17 at 15:00
  • https://plnkr.co/edit/o7GohlMJ5nxgJtMEgKEP?p=preview I think if you have `deps` then you need `useFactory` instead of `useValue`. – Günter Zöchbauer Aug 02 '17 at 15:14
  • Yep, but even when I use useFactory - I cannot inject http into this service - it's undefined. Is there any way to do that from this level? – magos Aug 02 '17 at 15:24
  • My updated Plunker shows that `http` is available. I also added a comment in the factory function why `http` wasn't available (`this.` doesn't work when the function is passed without `=>` or `.bind(startupService)`. – Günter Zöchbauer Aug 02 '17 at 15:28
  • Okay, thank you very much for that. And that example proves what I was talking about initially - that application does not wait for promise to be resolved with initialization. That is why I'm getting empty user object onInit AppComponent. https://plnkr.co/edit/5vmpjixyhhX3y4xfnByZ?p=preview – magos Aug 02 '17 at 15:46
  • Have you got any idea how to create solution where promise.resolve is before App component initialization? That's what is point of my question you marked as duplicate. – magos Aug 02 '17 at 15:58
  • That was my mistake. I forgot the `return` inside `=> {...` – Günter Zöchbauer Aug 02 '17 at 16:09
  • 1
    So you mean return () => { return startupService.load() }; yep? – magos Aug 02 '17 at 16:47
  • 1
    Yes, I updated the Plunker. – Günter Zöchbauer Aug 02 '17 at 16:50
  • @GünterZöchbauer: Can you please take a look at https://stackoverflow.com/questions/66785098/passing-bearer-token-to-a-different-app-initializer-to-load-config-from-server-i . I would really really appreciate your guidance – Samuel Mar 24 '21 at 16:25