У нас была эта проблема много лет назад, до того, как я присоединился к нам, и у нас было решение, которое использовало локальное хранилище для информации о пользователях и среде. Angular 1.0 дня, если быть точным. Раньше мы динамически создавали js-файл во время выполнения, который затем помещал сгенерированные URL-адреса api в глобальную переменную. В наши дни мы немного больше ориентируемся на ООП и не используем локальное хранилище ни для чего.
Я создал лучшее решение как для определения среды, так и для создания URL-адресов API.
Чем это отличается?
Приложение не загрузится, пока не будет загружен файл config.json. Он использует фабричные функции для создания более высокого уровня SOC. Я мог бы инкапсулировать это в службу, но я никогда не видел причины, по которой единственное сходство между различными разделами файла состоит в том, что они существуют вместе в файле. Наличие фабричной функции позволяет мне передавать функцию непосредственно в модуль, если он способен принять функцию. Наконец, мне проще настроить InjectionTokens, когда заводские функции доступны для использования.
Минусы?
Вам не повезло с этой настройкой (и большинством других ответов), если модуль, который вы хотите настроить, не позволяет передавать заводскую функцию в forRoot () или forChild (), и нет другого способа настроить пакет с помощью заводской функции.
инструкции
- Используя fetch для получения файла json, я сохраняю объект в окне и вызываю настраиваемое событие. - не забудьте установить whatwg-fetch и добавить его в свой polyfills.ts для совместимости с IE
- Попросите слушателя событий прослушивать настраиваемое событие.
- Слушатель событий получает событие, извлекает объект из окна для перехода к наблюдаемому и очищает то, что было сохранено в окне.
- Bootstrap Angular
- Вот здесь мое решение начинает действительно отличаться -
- Создайте файл, экспортирующий интерфейс, структура которого представляет ваш config.json - это действительно помогает с согласованностью типов, и в следующем разделе кода требуется тип, и не указывайте,
{}
или any
когда вы знаете, что можете указать что-то более конкретное
- Создайте объект BehaviorSubject, в который вы передадите проанализированный файл json на шаге 3.
- Используйте заводские функции, чтобы ссылаться на различные разделы вашей конфигурации для поддержки SOC
- Создайте InjectionTokens для поставщиков, которым нужен результат ваших фабричных функций
- и / или -
- Передайте фабричные функции непосредственно в модули, способные принимать функцию в методах forRoot () или forChild ().
- main.ts
Я проверяю, что window ["environment"] не заполнено перед созданием прослушивателя событий, чтобы разрешить возможность решения, в котором window ["environment"] заполняется другими способами до того, как код в main.ts когда-либо будет выполнен.
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));
});
}
--- среда-resolvers.ts
Я присваиваю значение BehaviorSubject, используя window ["environment"] для избыточности. Вы можете разработать решение, в котором ваша конфигурация уже предварительно загружена, а окно ["environment"] уже заполнено к моменту запуска любого кода вашего приложения Angular, включая код в main.ts
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
Дополнение
Как я решил создать свои URL-адреса API?
Я хотел понять, что делает каждый URL-адрес, через комментарий, и хотел проверить тип, поскольку это самая сильная сторона TypeScript по сравнению с javascript (IMO). Я также хотел создать для других разработчиков возможность добавлять новые конечные точки и API, которые были бы максимально удобными.
Я создал класс, который принимает среду (dev, test, stage, prod, "" и т. Д.), И передал это значение серии классов [1-N], задача которых - создать базовый URL-адрес для каждой коллекции API. . Каждый ApiCollection отвечает за создание базового URL-адреса для каждой коллекции API. Это могут быть наши собственные API, API поставщика или даже внешняя ссылка. Этот класс будет передавать созданный базовый URL-адрес в каждый последующий api, который он содержит. Прочтите приведенный ниже код, чтобы увидеть простой пример. После настройки другому разработчику очень просто добавить еще одну конечную точку в класс Api, не касаясь чего-либо еще.
TL; DR; основные принципы ООП и ленивые геттеры для оптимизации памяти
@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/");
}
}