Angular2: inject server side config into service
Asked Answered
N

3

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?

Nitz answered 28/9, 2016 at 10:7 Comment(3)
Sounds like #37612049 is what you're looking for.Danettedaney
@Nitz Did you managed to get this working based on the feedback from Günter ?Mackmackay
@Mackmackay Yes, by assigning appconfig to a global 'window' variable. Definietly not an elegant solution, but works for me.Nitz
T
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.

Tentacle answered 28/9, 2016 at 10:11 Comment(10)
I'm a bit confused about this line: useFactory: (config: ConfigService) => () => config.load() What is happening here?Nitz
Moreover APP_INITIALIZER cannot be founbd. Isn't it deprecated?Nitz
Should be exported by @angular/core angular.io/docs/ts/latest/api/core/index/…Danettedaney
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.htmlNitz
What do you mean with "from index.html". I assumed you want a config from the server.Danettedaney
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.Nitz
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.Danettedaney
If I remove AppConfig from providers in AppModule, then injecting it into DashboardService throws exception: 'No provider for AppConfig!'Nitz
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 })Danettedaney
This would only work where the window object is defined, such as in the browser but not if using nativescript.Gilbertson
M
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);
    }
}
Mackmackay answered 14/10, 2016 at 0:33 Comment(1)
Take a look at this solution to avoid global approach plnkr.co/edit/eg1Un1UhduBFGxksRf9P?p=previewHexamerous
R
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

Rinee answered 20/10, 2016 at 21:12 Comment(1)
Can also use a check to load settings based on user authenticated true|false or any other condition that is relevantRinee

© 2022 - 2024 — McMap. All rights reserved.