У нас була ця проблема кілька років тому, перш ніж я приєднався і мав рішення, яке використовувало локальне сховище для інформації про користувачів та середовище. Точніше - кутові 1,0 дні. Раніше ми динамічно створювали файл js під час виконання, який потім містив би створені URL-адреси API у глобальну змінну. У наші дні ми трохи більше керуємося ООП і ні для чого не використовуємо локальне сховище.
Я створив краще рішення як для визначення середовища, так і для створення URL-адреси.
Чим це відрізняється?
Додаток не завантажуватиметься, якщо не завантажено файл config.json. Він використовує заводські функції для створення вищого ступеня SOC. Я міг би інкапсулювати це у службу, але я ніколи не бачив жодної причини, коли єдиною подібністю між різними розділами файлу є те, що вони існують разом у файлі. Наявність заводської функції дозволяє мені передавати функцію безпосередньо в модуль, якщо він здатний прийняти функцію. Нарешті, мені простіше налаштувати InjectionTokens, коли доступні заводські функції.
Недоліки?
Вам не пощастило використовувати це налаштування (і більшість інших відповідей), якщо модуль, який ви хочете налаштувати, не дозволяє передавати заводську функцію ні forRoot (), ні forChild (), і немає іншого способу налаштуйте пакет за допомогою заводської функції.
Інструкції
- Використовуючи fetch для отримання файлу json, я зберігаю об’єкт у вікні та викликаю власну подію. - не забудьте встановити whatwg-fetch та додати його до вашого polyfills.ts для сумісності з IE
- Нехай слухач події прослуховує спеціальну подію.
- Прослуховувач подій отримує подію, отримує об'єкт із вікна, щоб перейти до спостережуваного, і очищає те, що було збережено у вікні.
- Бутстрап Кутовий
- Тут моє рішення починає по-справжньому відрізнятися -
- Створіть файл, що експортує інтерфейс, структура якого представляє ваш config.json - це дійсно допомагає з послідовністю типів, а в наступному розділі коду потрібен тип, і не вказуйте,
{}
або any
коли ви знаєте, що можете вказати щось більш конкретне
- Створіть BehaviourSubject, до якого ви передасте проаналізований файл json на кроці 3.
- Використовуйте заводські функції для посилання на різні розділи вашої конфігурації для підтримки SOC
- Створіть InjectionTokens для постачальників, які потребують результатів ваших заводських функцій
- та / або -
- Передайте заводські функції безпосередньо в модулі, здатні прийняти функцію в методах forRoot () або forChild ().
- main.ts
Я перевіряю, що вікно ["середовище"] не заповнюється перед створенням прослуховувача подій, щоб дозволити можливість рішення, коли вікно ["середовище"] заповнюється якимось іншим способом, перш ніж код у 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));
});
}
--- середовище-вирішувачі.ts
Я присвоюю значення BehaviourSubject, використовуючи вікно ["оточення"] для надмірності. Ви можете розробити рішення, де ваша конфігурація вже попередньо завантажена, а вікно ["оточення"] вже заповнене на час запуску будь-якого коду програми вашого 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). Я також хотів створити досвід для інших розробників, щоб додати нові кінцеві точки та apis, який був якомога безшовнішим.
Я створив клас, який приймає середовище (dev, test, stage, prod, "" та ін.) І передав це значення ряду класів [1-N], завданням яких є створення базової URL-адреси для кожної колекції API . Кожен ApiCollection відповідає за створення базової URL-адреси для кожної колекції API. Це можуть бути наші власні API, API постачальника або навіть зовнішнє посилання. Цей клас передаватиме створену базову URL-адресу в кожен наступний api, який він містить. Прочитайте наведений нижче код, щоб побачити приклад голих кісток. Після налаштування дуже просто для іншого розробника додати ще одну кінцеву точку до класу Api, не торкаючись нічого іншого.
TLDR; основні принципи ООП та ліниві здобувачі для оптимізації пам'яті
@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/");
}
}