Налаштування додатків - кутовий спосіб?


87

Я хочу додати App Settingsрозділ у свій додаток, де він буде містити деякі consts та заздалегідь визначені значення.

Я вже читав цю відповідь, яка використовує, OpaqueTokenале вона не підтримується в Angular. Ця стаття пояснює відмінності, але вона не наводить повного прикладу, і мої спроби не увінчалися успіхом.

Ось те, що я спробував (я не знаю, чи це правильно):

//ServiceAppSettings.ts

import {InjectionToken, OpaqueToken} from "@angular/core";

const CONFIG = {
  apiUrl: 'http://my.api.com',
  theme: 'suicid-squad',
  title: 'My awesome app'
};
const FEATURE_ENABLED = true;
const API_URL = new InjectionToken<string>('apiUrl');

І це компонент, де я хочу використовувати ці consts:

//MainPage.ts

import {...} from '@angular/core'
import {ServiceTest} from "./ServiceTest"

@Component({
  selector: 'my-app',
  template: `
   <span>Hi</span>
  ` ,  providers: [
    {
      provide: ServiceTest,
      useFactory: ( apiUrl) => {
        // create data service
      },
      deps: [

        new Inject(API_URL)
      ]
    }
  ]
})
export class MainPage {


}

Але це не працює, і я отримую помилки.

Питання:

Як я можу споживати значення "app.settings" значенням Angular?

плунжер

NB Звичайно, я можу створити ін’єкційну послугу та помістити її у постачальника NgModule, але, як я вже сказав, я хочу зробити це за InjectionTokenдопомогою Angular.


Ви можете перевірити мою відповідь тут, базуючись на чинній офіційній документації
JavierFuentes

@javier no. Проблема з вашим посиланням є, якщо два постачальники надають одне і те ж ім’я, тож у вас зараз проблема. Вхід opaquetoken
Royi Namir

ви знаєте [OpaqueToken застарілий]. ( angular.io/api/core/OpaqueToken ) Ця стаття розповідає про те, як запобігти зіткненню імен у кутових постачальниках
JavierFuentes

Так, я знаю, але все-таки пов'язана стаття неправильна.
Ройі Намір,

2
може бути посилання нижче може бути корисним для кожного, хто любить використовувати нову архітектуру схеми кутового конфігурації devblogs.microsoft.com/premier-developer/…
M_Farahmand

Відповіді:


56

Я зрозумів, як це зробити за допомогою InjectionTokens (див. Приклад нижче), і якщо ваш проект був побудований з використанням, Angular CLIви можете використовувати файли середовища, знайдені в /environmentsстатиці, application wide settingsяк кінцеву точку API, але в залежності від вимог вашого проекту ви, швидше за все використовуючи обидва, оскільки файли середовища - це просто об’єкти-літерали, тоді як ін’єкційна конфігурація, яка використовує InjectionToken, може використовувати змінні середовища, а оскільки це клас, може застосовуватися логіка для його налаштування на основі інших факторів у програмі, таких як вихідні дані запиту http, субдомен тощо

Приклад ін'єкційних токенів

/app/app-config.module.ts

import { NgModule, InjectionToken } from '@angular/core';
import { environment } from '../environments/environment';

export let APP_CONFIG = new InjectionToken<AppConfig>('app.config');

export class AppConfig {
  apiEndpoint: string;
}

export const APP_DI_CONFIG: AppConfig = {
  apiEndpoint: environment.apiEndpoint
};

@NgModule({
  providers: [{
    provide: APP_CONFIG,
    useValue: APP_DI_CONFIG
  }]
})
export class AppConfigModule { }

/app/app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppConfigModule } from './app-config.module';

@NgModule({
  declarations: [
    // ...
  ],
  imports: [
    // ...
    AppConfigModule
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

Тепер ви можете просто вписати його в будь-який компонент, послугу тощо:

/app/core/auth.service.ts

import { Injectable, Inject } from '@angular/core';
import { Http, Response } from '@angular/http';
import { Router } from '@angular/router';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';
import 'rxjs/add/observable/throw';

import { APP_CONFIG, AppConfig } from '../app-config.module';
import { AuthHttp } from 'angular2-jwt';

@Injectable()
export class AuthService {

  constructor(
    private http: Http,
    private router: Router,
    private authHttp: AuthHttp,
    @Inject(APP_CONFIG) private config: AppConfig
  ) { }

  /**
   * Logs a user into the application.
   * @param payload
   */
  public login(payload: { username: string, password: string }) {
    return this.http
      .post(`${this.config.apiEndpoint}/login`, payload)
      .map((response: Response) => {
        const token = response.json().token;
        sessionStorage.setItem('token', token); // TODO: can this be done else where? interceptor
        return this.handleResponse(response); // TODO:  unset token shouldn't return the token to login
      })
      .catch(this.handleError);
  }

  // ...
}

Потім ви також можете ввести перевірку конфігурації за допомогою експортованого AppConfig.


Ні, але ви можете буквально скопіювати та вставити першу частину у файл, імпортувати її у свій файл app.module.ts, а DI - де завгодно і вивести на консоль. Мені знадобилося б більше часу, щоб встановити це в планкарі, а потім він зробив би ці кроки.
mtpultz

О, я думав, у вас для цього вже є плюнкер :-) Дякую.
Рої Намір,


1
Я не вірю, що вам потрібно експортувати інтерфейс / клас AppConfig. Вам точно не потрібно використовувати його при виконанні DI. Щоб зробити цю роботу в одному файлі, це повинен бути клас, а не інтерфейс, але це не має значення. Насправді посібник із стилів пропонує замість інтерфейсів використовувати класи, оскільки це означає менше коду, і ви все одно можете набирати check, використовуючи їх. Що стосується його використання InjectionToken через загальні препарати, це те, що ви хочете включити.
mtpultz

1
Я намагаюся динамічно вводити кінцеву точку API, використовуючи змінні середовища Azure та функції перетворення JSON, але, схоже, ця відповідь просто отримує apiEndpoint з файлу середовища. Як би ви захопили його з конфігурації та експортували?
Арчібальд

138

Якщо ви використовуєте , є ще один варіант:

Angular CLI надає файли середовища у src/environments(типовими є environment.ts(dev) та environment.prod.ts(production)).

Зверніть увагу, що вам потрібно вказати параметри конфігурації у всіх environment.*файлах, наприклад,

environment.ts :

export const environment = {
  production: false,
  apiEndpoint: 'http://localhost:8000/api/v1'
};

environment.prod.ts :

export const environment = {
  production: true,
  apiEndpoint: '__your_production_server__'
};

і використовувати їх у своєму сервісі (правильний файл середовища вибирається автоматично):

api.service.ts

// ... other imports
import { environment } from '../../environments/environment';

@Injectable()
export class ApiService {     

  public apiRequest(): Observable<MyObject[]> {
    const path = environment.apiEndpoint + `/objects`;
    // ...
  }

// ...
}

Докладніше про середовища програм на Github (Angular CLI версія 6) або в офіційному посібнику Angular (версія 7) .


2
він працює нормально. Але під час переміщення збірки він також змінюється як пакет. Мені слід змінити конфігурацію в моїй службі не в коді після переходу на виробництво
Камалав,

42
Це є деяким анти-шаблоном у звичайній розробці програмного забезпечення; URL-адреса API - це просто конфігурація. Для повторного налаштування програми для іншого середовища не потрібно повторно збирати. Він повинен бути побудований один раз, розгорнутий багато разів (попереднє виготовлення, постановка, продакшн тощо).
Matt Tester

3
@MattTester Це насправді те, що зараз є офіційною історією Angular-CLI. Якщо у вас є краща відповідь на це питання: сміливо публікуйте його!
тило

7
це можна налаштувати після побудови ng?
NK

1
О гаразд, я неправильно прочитав коментарі. Я б погодився, що це надає анти-шаблон, я думав, є історія для динамічних конфігурацій часу роботи.
Йенс Бодал,

83

Не рекомендується використовувати environment.*.tsфайли для налаштування URL-адреси API. Здається, ви повинні, бо тут згадується слово "довкілля".

Використання цього є фактично конфігурацією часу компіляції . Якщо ви хочете змінити URL-адресу API, вам потрібно буде відновити. Це те, що вам не потрібно робити ... просто запитайте у свого доброзичливого відділу контролю якості :)

Вам потрібна конфігурація часу виконання , тобто програма завантажує свою конфігурацію під час запуску.

Деякі інші відповіді стосуються цього, але різниця полягає в тому, що конфігурацію потрібно завантажувати відразу після запуску програми , щоб звичайною службою вона могла використовуватись у будь-який час.

Щоб реалізувати конфігурацію виконання:

  1. Додайте конфігураційний файл JSON до /src/assets/папки (щоб скопіювати під час збірки)
  2. Створіть AppConfigServiceфайл для завантаження та розповсюдження конфігурації
  3. Завантажте конфігурацію за допомогою APP_INITIALIZER

1. Додайте файл конфігурації до /src/assets

Ви можете додати його в іншу папку, але вам потрібно повідомити CLI, що це актив у angular.json. Почніть із папки "активи":

{
  "apiBaseUrl": "https://development.local/apiUrl"
}

2. Створіть AppConfigService

Це послуга, яку буде вводити щоразу, коли вам знадобиться значення конфігурації:

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

  private appConfig: any;

  constructor(private http: HttpClient) { }

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

  // This is an example property ... you can make it however you want.
  get apiBaseUrl() {

    if (!this.appConfig) {
      throw Error('Config file not loaded!');
    }

    return this.appConfig.apiBaseUrl;
  }
}

3. Завантажте конфігурацію за допомогою APP_INITIALIZER

Щоб забезпечити AppConfigServiceбезпечну ін’єкцію, при повному завантаженні конфігурації, нам потрібно завантажити конфігурацію під час запуску програми. Важливо, що функція фабричної ініціалізації повинна повернути a, Promiseщоб Angular знав почекати, поки не закінчить вирішення перед завершенням запуску:

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    HttpClientModule
  ],
  providers: [
    {
      provide: APP_INITIALIZER,
      multi: true,
      deps: [AppConfigService],
      useFactory: (appConfigService: AppConfigService) => {
        return () => {
          //Make sure to return a promise!
          return appConfigService.loadAppConfig();
        };
      }
    }
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

Тепер ви можете вводити його куди завгодно, і вся конфігурація буде готова до читання:

@Component({
  selector: 'app-test',
  templateUrl: './test.component.html',
  styleUrls: ['./test.component.scss']
})
export class TestComponent implements OnInit {

  apiBaseUrl: string;

  constructor(private appConfigService: AppConfigService) {}

  ngOnInit(): void {
    this.apiBaseUrl = this.appConfigService.apiBaseUrl;
  }

}

Я не можу сказати це досить сильно, налаштування URL-адрес API, оскільки конфігурація часу компіляції є анти-шаблоном . Використовуйте конфігурацію виконання.


4
Локальний файл або інша служба, конфігурація часу компіляції не повинна використовуватися для URL-адреси API. Уявіть, якщо ваш додаток продається як продукт (покупець для встановлення), ви не хочете, щоб вони його компілювали і т. Д. У будь-якому випадку ви не хочете повторно скомпілювати те, що було побудовано 2 роки тому, просто тому, що URL-адреса API змінена. Ризик !!
Метт Тестер,

1
@Bloodhound Ви можете мати більше одного, APP_INITIALIZERале я не думаю, що ви легко можете зробити їх залежними один від одного. Схоже, у вас є гарне запитання, то, можливо, посилання на нього тут?
Метт Тестер

2
@MattTester - Якщо Angular коли-небудь реалізує цю функцію, це вирішить нашу проблему: github.com/angular/angular/issues/23279#issuecomment-528417026
Mike Becatti

2
@CrhistianRamirez Це з точки зору програми: конфігурація невідома до часу виконання, а статичний файл знаходиться поза збіркою і може бути встановлений різними способами під час розгортання. Статичний файл чудово підходить для нечутливих конфігурацій. API або будь-яка інша захищена кінцева точка можлива за допомогою тієї ж техніки, але як автентифікуватись, щоб зробити її захищеною - це ваша наступна проблема.
Метт Тестер,

1
@DaleK Читаючи між рядками, ви розгортаєте за допомогою Web Deploy. Якщо ви використовуєте конвеєр розгортання, наприклад Azure DevOps, тоді можна правильно встановити файл конфігурації як наступний крок. За налаштування конфігурації відповідає процес розгортання / конвеєр, який може замінити значення у конфігураційному файлі за замовчуванням. Сподіваюся, що з’ясовує.
Метт Тестер,

8

Ось моє рішення, завантаження з .json, щоб дозволити зміни без відновлення

import { Injectable, Inject } from '@angular/core';
import { Http } from '@angular/http';
import { Observable } from 'rxjs/Observable';
import { Location } from '@angular/common';

@Injectable()
export class ConfigService {

    private config: any;

    constructor(private location: Location, private http: Http) {
    }

    async apiUrl(): Promise<string> {
        let conf = await this.getConfig();
        return Promise.resolve(conf.apiUrl);
    }

    private async getConfig(): Promise<any> {
        if (!this.config) {
            this.config = (await this.http.get(this.location.prepareExternalUrl('/assets/config.json')).toPromise()).json();
        }
        return Promise.resolve(this.config);
    }
}

та config.json

{
    "apiUrl": "http://localhost:3000/api"
}

1
Проблема такого підходу полягає в тому, що config.json відкритий для світу. Як би ви завадили комусь вводити www.mywebsite.com/assetts/config.json?
Альберто Л. Бонфільо

1
@ AlbertoL.Bonfiglio, ви налаштовуєте сервер не дозволяти доступ ззовні до файлу config.json (або розміщуєте його в каталозі, який не має загальнодоступного доступу)
Алекс Пандреа

Це теж моє улюблене рішення, але все ж стурбоване ризиками безпеки.
Viqas

7
Будь ласка, чи можете ви допомогти мені це правильно зрозуміти? Як це більш ризиковано, ніж традиційне для кутових середовищ? Повний вміст environments.prod.tsafter у певний момент ng build --prodбуде в якомусь .jsфайлі. Навіть якщо вони затуманені, дані від environments.prod.tsбудуть у чистому тексті. І як і всі файли .js, він буде доступний на машині кінцевого користувача.
igann

5
@ AlbertoL.Bonfiglio Оскільки Angular додаток за своєю природою є клієнтським додатком, і JavaScript буде використовуватися для передачі даних та конфігурації, у ньому не повинно бути секретної конфігурації; всі секретні визначення конфігурації повинні знаходитись за рівнем API, де браузер користувача або інструменти браузера не можуть отримати до нього доступ. Значення, такі як базовий URI API, є загальнодоступними для загального доступу, оскільки API повинен мати власні облікові дані та безпеку, засновану на вході користувача (маркер носія через https).
Томмі Елліотт

4

Файл конфігурації бідної людини:

Додайте до вашого index.html як першу лінію в тезі body:

<script lang="javascript" src="assets/config.js"></script>

Додайте ресурси / config.js:

var config = {
    apiBaseUrl: "http://localhost:8080"
}

Додайте config.ts:

export const config: AppConfig = window['config']

export interface AppConfig {
    apiBaseUrl: string
}

Серйозно кажучи, +1 за кип'ятіння розчину до найосновніших компонентів і при цьому зберігає послідовність типу.
Світяться

4

Я виявив, що використання APP_INITIALIZERfor для цього не працює в ситуаціях, коли інші постачальники послуг вимагають введення конфігурації. Вони можуть бути створені перед APP_INITIALIZERзапуском.

Я бачив інші рішення, які використовують fetchдля читання файлу config.json і надання його за допомогою маркера ін'єкції в параметрі platformBrowserDynamic()до завантаження кореневого модуля. Але fetchпідтримується не у всіх браузерах, зокрема в браузерах WebView для мобільних пристроїв, на які я націлений.

Далі подано рішення, яке працює для мене як для PWA, так і для мобільних пристроїв (WebView). Примітка: Наразі я тестував лише в Android; робота вдома означає, що я не маю доступу до Mac для створення.

В main.ts:

import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
import { APP_CONFIG } from './app/lib/angular/injection-tokens';

function configListener() {
  try {
    const configuration = JSON.parse(this.responseText);

    // pass config to bootstrap process using an injection token
    platformBrowserDynamic([
      { provide: APP_CONFIG, useValue: configuration }
    ])
      .bootstrapModule(AppModule)
      .catch(err => console.error(err));

  } catch (error) {
    console.error(error);
  }
}

function configFailed(evt) {
  console.error('Error: retrieving config.json');
}

if (environment.production) {
  enableProdMode();
}

const request = new XMLHttpRequest();
request.addEventListener('load', configListener);
request.addEventListener('error', configFailed);
request.open('GET', './assets/config/config.json');
request.send();

Цей код:

  1. запускає асинхронний запит для config.jsonфайлу.
  2. Коли запит завершується, аналізує JSON на об’єкт Javascript
  3. надає значення за допомогою APP_CONFIGмаркера інжекції перед початком завантаження.
  4. І нарешті, завантажує кореневий модуль.

APP_CONFIGпотім можна вводити будь-яким додатковим постачальникам в, app-module.tsі це буде визначено. Наприклад, я можу ініціалізувати FIREBASE_OPTIONSмаркер введення @angular/fireтаким чином:

{
      provide: FIREBASE_OPTIONS,
      useFactory: (config: IConfig) => config.firebaseConfig,
      deps: [APP_CONFIG]
}

Я вважаю всю цю справу напрочуд важкою (і хакерською) справою за дуже поширеною вимогою. Сподіваємось, найближчим часом буде кращий спосіб, наприклад, підтримка фабрик постачальників асинхронних послуг.

Решта коду для повноти ...

В app/lib/angular/injection-tokens.ts:

import { InjectionToken } from '@angular/core';
import { IConfig } from '../config/config';

export const APP_CONFIG = new InjectionToken<IConfig>('app-config');

і app/lib/config/config.tsя визначаю інтерфейс для мого конфігураційного файлу JSON:

export interface IConfig {
    name: string;
    version: string;
    instance: string;
    firebaseConfig: {
        apiKey: string;
        // etc
    }
}

Конфігурація зберігається в assets/config/config.json:

{
  "name": "my-app",
  "version": "#{Build.BuildNumber}#",
  "instance": "localdev",
  "firebaseConfig": {
    "apiKey": "abcd"
    ...
  }
}

Примітка. Я використовую завдання Azure DevOps, щоб вставити Build.BuildNumber і замінити інші параметри для різних середовищ розгортання, коли воно розгортається.


2

Ось два мої рішення для цього

1. Зберігайте у файлах JSON

Просто створіть файл json і отримайте компонент $http.get()методом. Якщо мені це потрібно було дуже низько, то це добре і швидко.

2. Зберігайте, використовуючи послуги передачі даних

Якщо ви хочете зберігати та використовувати у всіх компонентах або маєте велике використання, тоді краще скористатися службою передачі даних. Подобається це :

  1. Просто створіть статичну папку всередині src/appпапки.

  2. Створіть файл з іменем як fuels.tsу статичну папку. Ви також можете зберігати тут інші статичні файли. Давайте визначимо ваші дані таким чином. Якщо припустити, що у вас є дані про паливо.

__

export const Fuels {

   Fuel: [
    { "id": 1, "type": "A" },
    { "id": 2, "type": "B" },
    { "id": 3, "type": "C" },
    { "id": 4, "type": "D" },
   ];
   }
  1. Створіть ім'я файлу static.services.ts

__

import { Injectable } from "@angular/core";
import { Fuels } from "./static/fuels";

@Injectable()
export class StaticService {

  constructor() { }

  getFuelData(): Fuels[] {
    return Fuels;
  }
 }`
  1. Тепер Ви можете зробити це доступним для кожного модуля

просто імпортуйте в файл app.module.ts, як цей, і змініть постачальників

import { StaticService } from './static.services';

providers: [StaticService]

Тепер використовуйте це як StaticServiceу будь-якому модулі.

Це все.


Хороше рішення, оскільки вам не потрібно перекомпілювати. Середовище схоже на жорстке кодування його в коді. Гадкий. +1
Terrance00


0

У нас була ця проблема кілька років тому, перш ніж я приєднався і мав рішення, яке використовувало локальне сховище для інформації про користувачів та середовище. Точніше - кутові 1,0 дні. Раніше ми динамічно створювали файл js під час виконання, який потім містив би створені URL-адреси API у глобальну змінну. У наші дні ми трохи більше керуємося ООП і ні для чого не використовуємо локальне сховище.

Я створив краще рішення як для визначення середовища, так і для створення URL-адреси.

Чим це відрізняється?

Додаток не завантажуватиметься, якщо не завантажено файл config.json. Він використовує заводські функції для створення вищого ступеня SOC. Я міг би інкапсулювати це у службу, але я ніколи не бачив жодної причини, коли єдиною подібністю між різними розділами файлу є те, що вони існують разом у файлі. Наявність заводської функції дозволяє мені передавати функцію безпосередньо в модуль, якщо він здатний прийняти функцію. Нарешті, мені простіше налаштувати InjectionTokens, коли доступні заводські функції.

Недоліки?

Вам не пощастило використовувати це налаштування (і більшість інших відповідей), якщо модуль, який ви хочете налаштувати, не дозволяє передавати заводську функцію ні forRoot (), ні forChild (), і немає іншого способу налаштуйте пакет за допомогою заводської функції.

Інструкції

  1. Використовуючи fetch для отримання файлу json, я зберігаю об’єкт у вікні та викликаю власну подію. - не забудьте встановити whatwg-fetch та додати його до вашого polyfills.ts для сумісності з IE
  2. Нехай слухач події прослуховує спеціальну подію.
  3. Прослуховувач подій отримує подію, отримує об'єкт із вікна, щоб перейти до спостережуваного, і очищає те, що було збережено у вікні.
  4. Бутстрап Кутовий

- Тут моє рішення починає по-справжньому відрізнятися -

  1. Створіть файл, що експортує інтерфейс, структура якого представляє ваш config.json - це дійсно допомагає з послідовністю типів, а в наступному розділі коду потрібен тип, і не вказуйте, {}або anyколи ви знаєте, що можете вказати щось більш конкретне
  2. Створіть BehaviourSubject, до якого ви передасте проаналізований файл json на кроці 3.
  3. Використовуйте заводські функції для посилання на різні розділи вашої конфігурації для підтримки SOC
  4. Створіть InjectionTokens для постачальників, які потребують результатів ваших заводських функцій

- та / або -

  1. Передайте заводські функції безпосередньо в модулі, здатні прийняти функцію в методах 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());

/*
  angular-cli only loads the first thing it finds it needs a dependency under /app in main.ts when under local scope. 
  Make AppModule the first dependency it needs and the rest are done for ya. Event listeners are 
  ran at a higher level of scope bypassing the behavior of not loading AppModule when the 
  configurationSubject is referenced before calling platformBrowserDynamic().bootstrapModule(AppModule)

  example: this will not work because configurationSubject is the first dependency the compiler realizes that lives under 
  app and will ONLY load that dependency, making AppModule an empty object.

  if(window["environment"])
  {
    if (window["environment"].production) {
      enableProdMode();
    }
    configurationSubject.next(window["environment"]);
    platformBrowserDynamic().bootstrapModule(AppModule)
    .catch(err => console.log(err));
  }
*/
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 {

  /**
  * This comment will show up when referencing this.apiConfig.apis.microservices.member.memberInfo
  */
  get MemberInfo() {
    this.attachProperty("MemberInfo", `${this.base}basic-info`);
    return this.MemberInfo;
  }

  constructor(baseUrl: string) {
    super(baseUrl + "member/api/");
  }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.