Ich habe eine app-const.ts mit einer Server-URL:

export class AppConst {
  public static serverPath = 'http://10.0.0.126:3031';
}

Dies ist ein URL-Pfad zum Spring Boot REST-Server. In diesem Fall halte ich diese Konstante an einer Stelle und verwende sie in allen Modulen. Nach einem Build kann ich diese Konstante jedoch nicht ändern, ohne das gesamte Projekt erneut zu erstellen, wenn die Server-URL geändert wird.

Gibt es eine Möglichkeit, diese Konstante in einer externen Konfigurationsdatei auf einem Hosting (neben index.html) zu halten, damit ich sie ändern kann, ohne das Projekt neu zu erstellen (wie application.properties Datei in Spring Boot, wer weiß)?

Oder wie kann ich die Situation mit einer sich ändernden Server-URL einfach verwalten?

Hinzufügung . Um die Situation zu klären: Ich platziere meinen Angular Web-Client auf einem Hosting. Anschließend beginnt dieser Client mit der Kommunikation mit einem Spring Boot REST-Server, der irgendwo platziert werden kann (z. B. in einer Cloud). Dieser Spring Boot-Server verfügt über eine Server-URL (serverPath), die manchmal geändert werden kann. Wenn sich die Server-URL ändert, muss ich diese serverPath-Konstante ändern und das gesamte Angular-Projekt nur aufgrund dieser Konstante neu erstellen.

7
Kirill Ch 18 Apr. 2018 im 18:02

4 Antworten

Beste Antwort

Ich habe eine folgende Lösung. Es verwendet eine externe JSON-Konfigurationsdatei.

Erstellen Sie also zuerst einen JSON im Assets / Data-Ordner .

config.json :

{ "serverPath": "http://10.0.0.126:3031" }}

Dann lesen und analysieren Sie es.

config.service.ts :

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';

import { Observable } from 'rxjs/Observable';

@Injectable()
export class ConfigService {

  private configUrl = "assets/data/config.json";

  constructor(private http: HttpClient) {
  }

  public getJSON(): Observable<any> {
    return this.http.get(this.configUrl)
  }

  public getSavedServerPath(){
    return localStorage.getItem('serverPath');
  }
}

In app.module.ts müssen Sie HttpClientModule importieren, damit dies funktioniert.

Anschließend können Sie serverPath beispielsweise in LocalStorage in der Anmeldekomponente speichern.

login.component.ts :

  constructor(public loginService:LoginService, public configService:ConfigService, private router: Router) {
  }

  ngOnInit() {

    this.configService.getJSON().subscribe(data => {
      localStorage.setItem("serverPath", data["serverPath"]);
    });

    ...
  }

Danach können Sie in all Ihren anderen Diensten auf den Serverpfad zugreifen.

server.service.ts :

import {Injectable } from '@angular/core';
import {Headers, Http, Response} from '@angular/http';
import 'rxjs/Rx';
import {Observable} from 'rxjs/Observable';
import {ConfigService} from '../services/config.service';

@Injectable()
export class ServerService {

  private serverPath:string;

  constructor(public configService: ConfigService, private http:Http) {
    this.serverPath = this.configService.getSavedServerPath();
  }
  ...
}

Nach einem Build sehen Sie die Datei assets / data / config.json in Ihrem Ordner dist . Kopieren Sie Ihren gesamten dist -Ordner auf Ihr Hosting und alles funktioniert.

4
Kirill Ch 11 Mai 2018 im 05:20

Eine andere Lösung wäre, es als Javascript-Variable in Ihre index.html-Datei einzufügen. Ich habe diese Methode verwendet und es funktioniert.

Fügen Sie es dem "head" -Teil Ihrer index.html mit den "script" -Tags hinzu. Beispiel:

<head>
  <script>
    window.LMS_REST_API_URL = "http://192.168.0.111:3000/";
  </script>
...

(Meine globale Variable heißt "LMS_REST_API_URL")

Danach können Sie wie folgt auf diese Variable zugreifen:

private lms_cli_URL = window["LMS_REST_API_URL"];

Ich habe es direkt von dem Dienst verwendet, der die URL benötigt, aber es wird wahrscheinlich auch in einer separaten App-const.ts-Klassendatei funktionieren, wie Sie sie verwenden.

1
Jeff Orange 2 Sept. 2018 im 10:55

Ich habe den Assets-Ordner verwendet, um auf die externe Konfiguration zu verweisen. Die Idee ist, dass der Bereitstellungsprozess die Konfiguration für diese Umgebung in den Assets-Ordner aktualisiert, der dann beim Start der App gelesen wird. Platzieren Sie dazu zunächst Ihre Konfiguration in src/app/assets/config.json. Z.B.

{
    "serverRoot":"https://my.server.root.com/"
}

Der Bereitstellungsprozess kann dann die Eigenschaft serverRoot in dieser Datei auf den richtigen Wert für diese Umgebung aktualisieren oder den Inhalt von config.json vollständig ersetzen.

Erstellen Sie dann in src/app/model/environment.ts eine Modellklasse, die einen typsicheren Zugriff auf die Konfiguration ermöglicht:

export class Environment {
    static serverRoot:string;

    static load(config:json) {
        this.serverRoot = config.serverRoot;
    }
}

Erstellen Sie dann einen Dienst, um die Konfiguration in src/app/services/environment-load.service.ts zu laden:

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Environment } from '../model/Environment';


@Injectable({
  providedIn: 'root'
})
export class EnvironmentLoadService {

  constructor(private http: HttpClient) { }

  init() {
    return this.http.get('assets/config.json').toPromise().then(data => {
      Environment.load(data);
    });
  }

  static initializeEnvironmentConfig = (appConfig: EnvironmentLoadService) => {
    return () => {
      return appConfig.init();
    };
  };
}

Legen Sie schließlich in Ihrem App-Modul (src/app/app.module.ts) EnvironmentLoadService als Provider fest, der während der App-Initialisierungsphase des App-Lebenszyklus erstellt wird. Dies garantiert, dass alle Versprechen gelöst werden, bevor die App-Initialisierungsphase abgeschlossen ist und dass Ihre Konfiguration vollständig geladen ist, bevor die erste Komponente erstellt wird:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule, APP_INITIALIZER } from '@angular/core';
import { AppComponent } from './app.component';
import { HttpClientModule } from '@angular/common/http';
import { EnvironmentLoadService } from './services/environment-load.service';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    HttpClientModule
  ],
  providers: [EnvironmentLoadService, {
    provide: APP_INITIALIZER,
    useFactory: EnvironmentLoadService.initializeEnvironmentConfig,
    multi: true,
    deps: [EnvironmentLoadService]
  }],
  bootstrap: [AppComponent]
})
export class AppModule { }
1
Chris Knight 18 Nov. 2019 im 07:47

Ich habe mehrere Anwendungen, die genau dies tun. Ich habe eine Dienstprogrammbibliothek für meine Anwendungen erstellt, die dies enthält.

Erstens habe ich eine "Konfigurations" -Klasse. Die JSON-Konfigurationsdatei wird vom Server geladen und einer Instanz dieser Klasse zugeordnet:

export class Configuration {
  [key: string]: any;
}

Dann gibt es den ConfigurationService, der für das Laden der Konfigurationsdatei verantwortlich ist:

import {APP_INITIALIZER, Injectable} from '@angular/core';
import {HttpClient} from '@angular/common/http';
import {Observable} from 'rxjs/Observable';
import {AsyncSubject} from 'rxjs/AsyncSubject';
import 'rxjs/observable/throw';
import {Configuration} from './configuration';

// synchronous version of the initializer - the app initialization will wait for the configuration to load
export function configurationServiceInitializerFactory(configurationService: ConfigurationService): Function {
  // a lambda is required here, otherwise `this` won't work inside ConfigurationService::load
  return () =>  configurationService.load(Synchronicity.Sync);
}

// async version of the initializer - the app initialization will proceed without waiting for the configuration to load
export function asyncConfigurationServiceInitializerFactory(configurationService: ConfigurationService): Function {
  // a lambda is required here, otherwise `this` won't work inside ConfigurationService::load
  return () =>  {
    configurationService.load(Synchronicity.Async);
    return null;
  };
}

export const enum Synchronicity {
  Sync,
  Async,
  Unknown
}

@Injectable()
export class ConfigurationService {

  private synchronicity: Synchronicity = Synchronicity.Unknown;

  // the observable from the (load) http call to get the configuration
  private httpObservable: Observable<Configuration>;

  // the error (if any) that occurred during the load
  private loadError;

  private loadAttempted = false;
  private hasError = false;
  private loaded = false;

  // Observable that makes the config available to consumers when using async initialization
  private loadSubject = new AsyncSubject<Configuration>();

  // the configuration
  private configuration: Configuration;

  constructor(private http: HttpClient) {
  }

  public hasLoadError(): boolean {
    return this.hasError;
  }

  public isLoadead(): boolean {
    return this.loaded;
  }

  // use this when you have initialized with the (synchronous) configurationServiceInitializerFactory
  public getConfig(): Configuration {
    if(!this.loadAttempted) {
      throw new Error('ConfigurationService.getConfig() - service has not been iniialized yet');
    }

    if(this.synchronicity === Synchronicity.Async) {
      throw new Error('ConfigurationService.getConfig() - service has been iniialized async - use getConfigurationObserable()');
    }

    if(this.hasError) {
      throw this.loadError;
    }

    if(!this.loaded) {
      throw new Error('ConfigurationService.getConfig() - service has not finished loading the config');
    }

    return this.configuration;
  }

  // use this when you have initialized with the asyncCnfigurationServiceInitializerFactory
  public getConfigObservable(): Observable<Configuration> {

    // if neither init function was used, init async
    if (!this.loadAttempted) {
      this.load(Synchronicity.Async);
    }
    return this.loadSubject;
  }

  // the return value (Promise) of this method is provided via the APP_INITIALIZER Injection Token,
  // so the application's initialization will not complete until the Promise resolves.
  public load(synchronicity: Synchronicity): Promise<Configuration> {
    if (!this.loadAttempted) {
      this.loadAttempted = true;
      this.synchronicity = synchronicity;
      this.httpObservable = this.http.get<Configuration>('config/ui-config.json'); // path is relative to that for app's index.html
      this.httpObservable.subscribe(
        config => {
          this.configuration = config;
          this.loadError = undefined;
          this.hasError = false;
          this.loadSubject.next(this.configuration);
          this.loadSubject.complete();
          this.loaded = true;
        },
        error => {
          this.loadError = error;
          this.hasError = true;
          this.loadSubject.error(error);
          this.loadSubject.complete();
        }
      );
      return this.httpObservable.toPromise();
    }
  }
}

Wie Sie sehen können, bezieht dieser Dienst die Konfiguration von einem relativen Pfad, config / ui-config.json. Der Pfad bezieht sich auf die Datei index.html, die zum Booten der Anwendung geladen wurde. Sie müssen dafür sorgen, dass der Server die Konfigurationsdatei von diesem Speicherort zurückgibt.

Der Dienst wird in die Initialisierungssequenz von Angular eingebunden (Code folgt). Dies kann entweder synchron oder asynchron in Bezug auf die Initialisierung der App erfolgen.

Wenn Sie die 'synchrone' Methode verwenden, wird die App-Initialisierung angehalten, während die JSON-Datei geladen wird. Der Vorteil dabei ist, dass nach Abschluss der Initialisierung der App bekannt ist, dass die Konfiguration verfügbar ist. Der Nachteil ist die möglicherweise lange Pause während der Initialisierung, in der Ihr Benutzer eine leere Seite betrachtet.

Wenn Sie die 'asynchrone' Methode verwenden, startet die App-Initialisierung nur die Anforderung für die Konfigurationsdatei, hält jedoch nicht an, um auf den Abschluss dieser Anforderung zu warten. Oberseite: schnelle (normale) Initialisierung. Nachteil: Sie erhalten ein Observable der Konfiguration anstelle einer Konfiguration. Daher müssen Sie FlatMap (mergeMap) über dieses Observable überall dort übertragen, wo Sie die Konfiguration benötigen.

So wird es in die App-Initialisierung in app.module eingebunden:

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    // etc.
  ],
  providers: [
    ConfigurationService,
    { provide: APP_INITIALIZER, useFactory: asyncConfigurationServiceInitializerFactory, deps: [ConfigurationService], multi: true },
  ],
  bootstrap: [AppComponent]
})
export class AppModule {
}

Das ist ein Beispiel für die asynchrone Konfiguration. Verwenden Sie für die Synchronisierung einfach configurationServiceInitializerFactory anstelle von asyncConfigurationServiceInitializerFactory

Wenn Sie die synchrone Version verwenden, können Sie den ConfigurationService einfach in Ihre Dienste einfügen und die Methode getConfig() aufrufen.

Wenn Sie die asynchrone Version verwenden, fügen Sie den ConfigurationService weiterhin in Ihre Dienste ein, müssen dann jedoch Folgendes tun:

getSomething(): Observable<Something> {
    return this.configurationService.getConfigObservable().mergeMap(config =>
      this.http.get<Something>(`${config.serviceRoot}/something`)
    );
}

Edit: Oh, ich hätte fast vergessen, ich habe vor einiger Zeit einen Blog-Beitrag dazu geschrieben, und das hat ein bisschen mehr Details. Es befindet sich unter https://chariotsolutions.com/blog / post / 12-Faktor-ish-Konfiguration-von-Winkel-Anwendungen /

Ein vollständiges Beispiel für GitHub finden Sie unter https://github.com/rfreedman/angular-configuration- Service

0
GreyBeardedGeek 19 Apr. 2018 im 05:04