多年前,我加入并提出了一个使用本地存储来存储用户和环境信息的解决方案之前,我们就遇到了这个问题。精确的角度为1.0天。我们以前是在运行时动态创建一个js文件,然后将生成的api网址放入一个全局变量中。如今,我们受OOP驱动更多,并且不使用本地存储来存储任何内容。
我为确定环境和api URL创建了一个更好的解决方案。
这有何不同?
除非已加载config.json文件,否则不会加载该应用程序。它使用工厂功能来创建更高级别的SOC。我可以将其封装到服务中,但是当文件的不同部分之间唯一的相似之处在于它们在文件中同时存在时,我再也看不到任何原因。如果具有工厂功能,则可以将功能直接传递到模块中(如果它可以接受功能)。最后,当工厂函数可供使用时,我可以更轻松地设置InjectionTokens。
缺点?
如果您要配置的模块不允许将工厂函数传递给forRoot()或forChild(),那么您将无法使用此设置(以及大多数其他答案),并且没有其他方法可以使用出厂功能配置程序包。
使用说明
- 使用获取来检索json文件,我将对象存储在window中并引发一个自定义事件。-记住安装whatwg-fetch并将其添加到您的polyfills.ts中以实现IE兼容性
- 让事件监听器监听自定义事件。
- 事件侦听器接收事件,从窗口检索对象以传递给可观察对象,并清除窗口中存储的内容。
- 引导角
-这是我的解决方案开始与众不同的地方-
- 创建一个文件,导出一个接口,该接口的结构表示您的config.json -确实有助于类型一致性,下一部分代码需要一个类型,并且不指定,
{}
或者any
在您知道可以指定更具体的内容时
- 创建BehaviorSubject,您将在步骤3中将解析的json文件传递到其中。
- 使用工厂功能来引用配置的不同部分以维护SOC
- 为需要工厂功能结果的提供者创建InjectionTokens
-和/或-
- 将工厂函数直接传递到能够在其forRoot()或forChild()方法中接受函数的模块中。
-main.ts
我在创建事件侦听器之前检查是否未填充window [“ environment”],以允许通过main.ts中的代码在执行之前通过其他某种方式填充window [“ environment”]的解决方案。
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
import { configurationSubject } from './app/utils/environment-resolver';
var configurationLoadedEvent = document.createEvent('Event');
configurationLoadedEvent.initEvent('config-set', true, true);
fetch("../../assets/config.json")
.then(result => { return result.json(); })
.then(data => {
window["environment"] = data;
document.dispatchEvent(configurationLoadedEvent);
}, error => window.location.reload());
if(!window["environment"]) {
document.addEventListener('config-set', function(e){
if (window["environment"].production) {
enableProdMode();
}
configurationSubject.next(window["environment"]);
window["environment"] = undefined;
platformBrowserDynamic().bootstrapModule(AppModule)
.catch(err => console.log(err));
});
}
---环境解析器
我使用window [“ environment”]为BehaviorSubject分配了一个值,以实现冗余。您可以设计一个解决方案,其中在运行任何Angular应用程序代码时(包括main.ts中的代码),已经预先加载了配置,并且window [“ environment”]已被填充。
import { BehaviorSubject } from "rxjs";
import { IConfig } from "../config.interface";
const config = <IConfig>Object.assign({}, window["environment"]);
export const configurationSubject = new BehaviorSubject<IConfig>(config);
export function resolveEnvironment() {
const env = configurationSubject.getValue().environment;
let resolvedEnvironment = "";
switch (env) {
// case statements for determining whether this is dev, test, stage, or prod
}
return resolvedEnvironment;
}
export function resolveNgxLoggerConfig() {
return configurationSubject.getValue().logging;
}
-app.module.ts-精简以便于理解
有趣的事实!较旧的NGXLogger版本要求您将一个对象传递到LoggerModule.forRoot()。实际上,LoggerModule仍然可以!NGXLogger请公开LoggerConfig,您可以重写它,从而允许使用工厂功能进行设置。
import { resolveEnvironment, resolveNgxLoggerConfig, resolveSomethingElse } from './environment-resolvers';
import { LoggerConfig } from 'ngx-logger';
@NgModule({
modules: [
SomeModule.forRoot(resolveSomethingElse)
],
providers:[
{
provide: ENVIRONMENT,
useFactory: resolveEnvironment
},
{
provide: LoggerConfig,
useFactory: resolveNgxLoggerConfig
}
]
})
export class AppModule
附录
如何解决我的API网址的创建?
我希望能够通过注释来理解每个URL的功能,并希望进行类型检查,因为与JavaScript(IMO)相比,TypeScript的最大优势是。我还想为其他开发人员创造一种体验,以添加尽可能无缝的新端点和api。
我创建了一个包含环境的类(开发,测试,阶段,产品,“”等),并将此值传递给一系列类[1-N],其工作是为每个API集合创建基本URL。 。每个ApiCollection负责为每个API集合创建基本URL。可能是我们自己的API,供应商的API,甚至是外部链接。该类会将创建的基本URL传递到它包含的每个后续api中。阅读下面的代码以查看示例。设置完成后,对于另一个开发人员而言,将另一个端点添加到Api类非常简单,而无需进行任何其他操作。
TLDR;基本的OOP原理和用于内存优化的惰性获取器
@Injectable({
providedIn: 'root'
})
export class ApiConfig {
public apis: Apis;
constructor(@Inject(ENVIRONMENT) private environment: string) {
this.apis = new Apis(environment);
}
}
export class Apis {
readonly microservices: MicroserviceApiCollection;
constructor(environment: string) {
this.microservices = new MicroserviceApiCollection(environment);
}
}
export abstract class ApiCollection {
protected domain: any;
constructor(environment: string) {
const domain = this.resolveDomain(environment);
Object.defineProperty(ApiCollection.prototype, 'domain', {
get() {
Object.defineProperty(this, 'domain', { value: domain });
return this.domain;
},
configurable: true
});
}
}
export class MicroserviceApiCollection extends ApiCollection {
public member: MemberApi;
constructor(environment) {
super(environment);
this.member = new MemberApi(this.domain);
}
resolveDomain(environment: string): string {
return `https://subdomain${environment}.actualdomain.com/`;
}
}
export class Api {
readonly base: any;
constructor(baseUrl: string) {
Object.defineProperty(this, 'base', {
get() {
Object.defineProperty(this, 'base',
{ value: baseUrl, configurable: true});
return this.base;
},
enumerable: false,
configurable: true
});
}
attachProperty(name: string, value: any, enumerable?: boolean) {
Object.defineProperty(this, name,
{ value, writable: false, configurable: true, enumerable: enumerable || true });
}
}
export class MemberApi extends Api {
get MemberInfo() {
this.attachProperty("MemberInfo", `${this.base}basic-info`);
return this.MemberInfo;
}
constructor(baseUrl: string) {
super(baseUrl + "member/api/");
}
}