1

I'm using Angular 2.0.0 with TypeScript in ASP.NET Core. My goal is to create AppConfig service in my app, based on server-side variables. With a help from few other answers, I was able to create following code:

Index.cshtml

<app>
    <i class="fa fa-spin fa-5x fa-spinner"></i>
</app>

<script>
    System.import('/app/main').then((m) => {
        var config = {
            apiUrl: @options.ApiServerUrl
        };

        m.RunApplication(config);
    }, console.error.bind(console));
</script>

app.config.ts

import { Injectable } from "@angular/core";

@Injectable()
export class AppConfig {
    apiUrl: string;
}

main.ts

import { platformBrowserDynamic } from "@angular/platform-browser-dynamic";

import { AppModule } from "./app.module";
import { AppConfig } from "./app.config";

export function RunApplication(config: Object) {

    var appConfig = new AppConfig();
    appConfig.apiUrl = config["apiUrl"];

    console.log('Created config: ', appConfig);

    platformBrowserDynamic()
        .bootstrapModule(AppModule, [{ providers: [{ provide: AppConfig, useValue: appConfig }] }])
        .catch(err => console.error(err));
}

app.module.ts

import { NgModule } from "@angular/core";
import { BrowserModule } from "@angular/platform-browser";
import { HttpModule } from "@angular/http";
import { AppRouting, AppRoutingProviders } from "./app.routes";
import { AppConfig } from "./app.config";
import { AppComponent } from "./app.component";
import { DashboardComponent } from "./dashboard/dashboard.component";
import { DashboardService } from "./dashboard/dashboard.service";

@NgModule({
    declarations: [
        AppComponent,
        DashboardComponent
    ],
    imports: [
        BrowserModule,
        HttpModule,
        AppRouting
    ],
    providers: [
        AppRoutingProviders,
        AppConfig,
        DashboardService
    ],
    bootstrap: [AppComponent],
})
export class AppModule { }

dashboard.service.ts

import { Http } from "@angular/http";
import { Injectable } from "@angular/core";
import { Observable } from "rxjs/Rx";
import "rxjs/add/operator/map";
import { AppConfig } from "../app.config";

@Injectable()
export class DashboardService {

    constructor(private appConfig: AppConfig, private http: Http) {
        console.log('Injected config: ', appConfig);
        console.log('Injected apiUrl: ', appConfig.apiUrl);
    }
}

Outpup from Chrome console

Outpup from Chrome console

As you can see for some reason created and injected AppConfig are not the same, and apiUrl value does not appear in DashboardService. I suspect that error is somewhere in here:

bootstrapModule(AppModule, [{ providers: [{ provide: AppConfig, useValue: appConfig }] }])

but I'm quite new to Angular2 and don't know how to fix it. Can you point me where the problem is?

vebbo
  • 240
  • 5
  • 14
  • Sounds like http://stackoverflow.com/questions/37611549/how-to-pass-parameters-rendered-from-backend-to-angular2-bootstrap-method is what you're looking for. – Günter Zöchbauer Sep 28 '16 at 10:10
  • @vebbo Did you managed to get this working based on the feedback from Günter ? – SamJackSon Oct 14 '16 at 00:00
  • @SamJackSon Yes, by assigning appconfig to a global 'window' variable. Definietly not an elegant solution, but works for me. – vebbo Oct 15 '16 at 22:09

3 Answers3

1

Your AppConfig provider in @NgModule() shadows the provider passed to bootstrapModule()

With How to pass parameters rendered from backend to angular2 bootstrap method you should get what you want.

Community
  • 1
  • 1
Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • I'm a bit confused about this line: ``useFactory: (config: ConfigService) => () => config.load()`` What is happening here? – vebbo Sep 28 '16 at 10:28
  • Moreover APP_INITIALIZER cannot be founbd. Isn't it deprecated? – vebbo Sep 28 '16 at 10:54
  • Should be exported by `@angular/core` https://angular.io/docs/ts/latest/api/core/index/APP_INITIALIZER-let.html – Günter Zöchbauer Sep 28 '16 at 10:59
  • I was able to do this `providers: [ AppRoutingProviders, AppConfig, { provide: APP_INITIALIZER, useFactory: (config: AppConfig) => () => { config.apiUrl = "http://localhost:26264"; return config; }, deps: [AppConfig], multi: true }, DashboardService ],` in app.module.ts but I still don't see how this helps me to pass params from index.html – vebbo Sep 28 '16 at 13:52
  • What do you mean with "from index.html". I assumed you want a config from the server. – Günter Zöchbauer Sep 28 '16 at 14:02
  • I meant my config is rendered by Razor, see updated Index.cshtml file and @options.ApiServerUrl, then I want to pass this value to my app. – vebbo Sep 28 '16 at 14:22
  • In this case you don't need `AppInitializer`. Just ensure you have only one provider for `AppConfig` and mentioned at the beginning of my answer. – Günter Zöchbauer Sep 28 '16 at 14:25
  • If I remove AppConfig from providers in AppModule, then injecting it into DashboardService throws exception: 'No provider for AppConfig!' – vebbo Sep 28 '16 at 15:27
  • 1
    This means `bootstrapModule(AppModule, ...)` doesn't accept providers for `...`. It's actually `CompilerOptions` that are expected as 2nd parameter. You need to move this provider to `@NgModule()`. You could assign it to a property on `window` (`window.appConfig = appConfig;`) and then in `@NgModule(...)` read it again from there (`{ provide: AppConfig, useValue: window.appConfig }`) – Günter Zöchbauer Sep 28 '16 at 15:36
  • This would only work where the window object is defined, such as in the browser but not if using nativescript. – Jens Bodal Nov 17 '18 at 06:48
0

I ended up adding object to globals.

// ===== File globals.ts

import { AppConfig } from './app.config';
'use strict';
export var appConfig: AppConfig;

// ===== File app.config.ts

import { Injectable } from "@angular/core";
@Injectable()
export class AppConfig {
    entityId: string;
    formId: string;
}

// ===== File index.html or cshtml

<script>
    System.import('app').then((m) => {
        var config = {
            entityId: '12',
            formId: '22'
        };
        m.RunApplication(config);
        },
        console.error.bind(console)
    );
</script>

// ===== File main.ts

import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

import { AppModule } from './app.module';
import { AppConfig } from './app.config';
import myGlobals = require('./globals');

//platformBrowserDynamic().bootstrapModule(AppModule);

export function RunApplication(config: Object) {

    var appConfig = new AppConfig();
    appConfig.entityId = config["entityId"];
    appConfig.formId = config["formId"];

    console.log('Created config: ', appConfig, appConfig.entityId, appConfig.formId);

    myGlobals.appConfig = appConfig;

    platformBrowserDynamic()
        .bootstrapModule(AppModule)
        .catch(err => console.error(err));
}

// ===== File app.module.ts

import { AppConfig } from './app.config';
import myGlobals = require('./globals');
...

@NgModule({
    imports: [
        ...
    ],
    declarations: [
        ...
    ],
    providers: [
        {
            provide: AppConfig,
            useValue: myGlobals.appConfig
        }
    ],
    bootstrap: [AppComponent]
})

// ===== File intro.component.ts

import { AppConfig } from "./app.config";
import myGlobals = require('./globals');
@Component({
    selector: 'my-intro,[my-intro]',
    templateUrl: ''
})
export class IntroComponent {
    constructor() {
        console.log('constructor', 'appConfig', myGlobals.appConfig);
    }
}
SamJackSon
  • 1,071
  • 14
  • 19
0

Guard your route with a CanActivate class using a Promise which loads the config settings should also work.

Use the appSettings.service with a function just like the one returning a promise

getAppSettings(): Promise<any> {
        var observable = this.http.get(this.ApiUrl, { headers: this.headers })
            .map((response: Response) => {
                var res = response.json();
                return res;
            });

        observable.subscribe(config => {
        this.config= config;
        console.log(this.config)
        });
        return observable.toPromise();  
    }

And the CanActivate guard as below:

import { Injectable } from '@angular/core';
import { Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { AppSettingsService } from './appsettings.service';

@Injectable()
export class CanActivateViaAuthGuard implements CanActivate {

//router: Router
    constructor(private appSettingsService: AppSettingsService)
    {
    }

    canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
               return this.appSettingsService.getAppSettings().then(() => { 
    return true });
   }
}

This will ensure that your settings are available when the corresponding components are constructed. (using the APP_INITIALIZER did not restrict the constructor being called, so I had to use this technic, Also please make sure, you dont export all the components in the exports:[] in the module)

To guard the routes and ensure settings are loaded before the constructors are called, please use the usual canActivate option in the path for the route definition

 path: 'abc',
 component: AbcComponent,
 canActivate: [CanActivateViaAuthGuard]

The initialization of appsettings should happen before the constructor for AbcComponent is called, this is tested and works in Angular 2.0.1

abhijoseph
  • 307
  • 1
  • 3
  • 5
  • Can also use a check to load settings based on user authenticated true|false or any other condition that is relevant – abhijoseph Oct 20 '16 at 21:28