我有一个带有服务器URL的app-const.ts:
export class AppConst {
public static serverPath = 'http://10.0.0.126:3031';
}
这是Spring Boot REST服务器的URL路径。在这种情况下,我将此常量保持在一个位置并在所有模块中使用它。但是,如果更改了服务器URL,那么在构建之后,如果不重新构建整个项目,我就无法更改此常量。
有没有办法在托管(index.html旁边)的某个外部配置文件中保持这个常量,这样我就可以在不重建项目的情况下更改它(比如Spring Boot中的application.properties文件,谁知道)?
或者我如何通过更改服务器URL轻松管理情况?
加成。清除情况:我将Angular Web客户端放在托管上。然后,此客户端开始与可以放置在某处(例如,在云中)的Spring Boot REST服务器进行通信。此Spring Boot服务器有一个服务器URL(serverPath),有时可能会更改。现在,如果服务器URL更改,我需要更改此serverPath常量并仅由于此常量重建整个Angular项目。
我有一个以下的解决方案。它使用外部JSON配置文件。
首先在assets / data文件夹中创建一个JSON。
config.json:
{“serverPath”:“http://10.0.0.126:3031”}
然后阅读并解析它。
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');
}
}
在app.module.ts中,您需要导入HttpClientModule以便这样做。
然后,您可以将登录组件中的serverPath保存在LocalStorage中。
login.component.ts:
constructor(public loginService:LoginService, public configService:ConfigService, private router: Router) {
}
ngOnInit() {
this.configService.getJSON().subscribe(data => {
localStorage.setItem("serverPath", data["serverPath"]);
});
...
}
之后,您可以在所有其他服务中访问服务器路径。
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();
}
...
}
构建之后,您将在dist文件夹中看到assets / data / config.json文件。将所有dist文件夹复制到您的主机并且所有工作。
另一种解决方案是将其添加为index.html文件中的javascript变量。我使用这种方法,它的工作原理。
使用“script”标记将其添加到index.html的“head”部分,例如:
<head>
<script>
window.LMS_REST_API_URL = "http://192.168.0.111:3000/";
</script>
...
(我的全局变量名为“LMS_REST_API_URL”)
在此之后,您可以像这样访问此变量:
private lms_cli_URL = window["LMS_REST_API_URL"];
我直接从需要URL的服务中使用它,但它可能也适用于你正在使用的单独的app-const.ts类文件。
我有几个应用程序正是这样做的。我为我的应用程序构建了一个实用程序库,其中包含了这个库。
首先,我有一个“配置”类。 json配置文件从服务器加载并映射到此类的实例:
export class Configuration {
[key: string]: any;
}
然后,有ConfigurationService,它负责加载配置文件:
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();
}
}
}
如您所见,此服务从相对路径config / ui-config.json获取配置。该路径相对于为引导应用程序而加载的index.html文件。您需要安排服务器从该位置返回配置文件。
该服务将挂钩到Angular的初始化序列(后面的代码)。它可以与应用程序的初始化同步或异步完成。
如果使用“同步”方法,则在加载json文件时,应用程序初始化将暂停。这样做的好处是,一旦应用程序完成初始化,就知道配置可用。缺点是初始化期间潜在的长暂停,用户正在查看空白页面。
如果您使用'异步'方法,则应用程序初始化将仅启动对配置文件的请求,但不会暂停以等待该请求完成。上行:快速(正常)初始化。缺点:您获得了配置的Observable而不是Configuration,因此您需要在需要配置的所有Observable上进行flatMap(mergeMap)。
以下是app.module中如何将其连接到应用程序初始化:
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
// etc.
],
providers: [
ConfigurationService,
{ provide: APP_INITIALIZER, useFactory: asyncConfigurationServiceInitializerFactory, deps: [ConfigurationService], multi: true },
],
bootstrap: [AppComponent]
})
export class AppModule {
}
这是异步配置的一个例子。对于同步,只需使用configurationServiceInitializerFactory
而不是asyncConfigurationServiceInitializerFactory
同样,如果您使用同步版本,您可以将ConfigurationService注入您的服务,并将其命名为getConfig()
方法。
如果您使用异步版本,您仍然会将ConfigurationService注入您的服务,但是您需要执行以下操作:
getSomething(): Observable<Something> {
return this.configurationService.getConfigObservable().mergeMap(config =>
this.http.get<Something>(`${config.serviceRoot}/something`)
);
}
编辑:哦,我差点忘了,我前一段时间做了一篇博文,而且还有一些细节。它在https://chariotsolutions.com/blog/post/12-factor-ish-configuration-of-angular-applications/
在https://github.com/rfreedman/angular-configuration-service的GitHub上有一个完整的例子