Конфигурация приложений на Angular. Лучшие практики

Как управлять файлами конфигурации среды и целями

Когда вы создали angular приложение с помощью Angular CLI или Nrwl Nx tools у вас всегда есть папка с фалами конфигурации окружения:

<APP_FOLDER>/src/environments/                        └──environment.ts                        └──environment.prod.ts

Можно переименовать environment.prod.ts в environment.production.ts например, также можно создавать дополнительные файлы конфигурации такие как environment.qa.ts или environment.staging.ts.

<APP_FOLDER>/src/environments/                        └──environment.ts                        └──environment.prod.ts                        └──environment.qa.ts                        └──environment.staging.ts

Файл environment.ts используется по умолчанию. Для использования остальных файлов необходимо открыть angular.json и настроить fileReplacements секцию в build конфигурации и добавить блоки в serve и е2е конфигурации.

{    "architect":{       "build":{          "configurations":{             "production":{                "fileReplacements":[                   {                      "replace":"<APP_FOLDER>/src/environments/environment.ts",                      "with":"<APP_FOLDER>/src/environments/environment.production.ts"                   }                ]             },             "staging":{                "fileReplacements":[                   {                      "replace":"<APP_FOLDER>/src/environments/environment.ts",                      "with":"<APP_FOLDER>/src/environments/environment.staging.ts"                   }                ]             }          }       },       "serve":{          "configurations":{             "production":{                "browserTarget":"app-name:build:production"             },             "staging":{                "browserTarget":"app-name:build:staging"             }          }       },       "e2e":{          "configurations":{             "production":{                "browserTarget":"app-name:serve:production"             },             "staging":{                "browserTarget":"app-name:serve:staging"             }          }       }    } }

Для сборки или запуска приложения с конкретным окрудением используйте команды:

ng build --configuration=staging ng start --configuration=staging ng e2e --configuration=staging  Кстати ng build --prod  всего лишь сокращенный вариант ng build --configuration=production

Не используйте environment файлы напрямую, только через DI

Использование глобальных переменных и прямых импортов нарушает ООП подход и усложняет тестируемость ваших классов. Поэтому лучше создать сервис который можно инжектить в ваши компоненты и другие сервисы. Вот пример такого сервиса с возможностью указывать дефолтное значение.

export const ENVIRONMENT = new InjectionToken<{ [key: string]: any }>('environment');  @Injectable({   providedIn: 'root', }) export class EnvironmentService {   private readonly environment: any;    // We need @Optional to be able start app without providing environment file   constructor(@Optional() @Inject(ENVIRONMENT) environment: any) {     this.environment = environment !== null ? environment : {};   }    getValue(key: string, defaultValue?: any): any {     return this.environment[key] || defaultValue;   } }  @NgModule({   imports: [     BrowserModule,     HttpClientModule,     AppRoutingModule,   ],   declarations: [     AppComponent,   ],   // We declare environment as provider to be able to easy test our service   providers: [{ provide: ENVIRONMENT, useValue: environment }],   bootstrap: [AppComponent], }) export class AppModule { }

Отделяйте конфигурацию окружения и бизнес логики

Конфигурация окружения включает в себя только свойства которые относятся к окружению, например apiUrl. В идеале конфигурация окружения должна состоять из двух свойств:

export const environment = {   production: true,   apiUrl: 'https://api.url', };

Также в этот конфиг можно добавить свойство для включения дебаг режима debugMode: true или можно добавить имя сервера на котором запущено приложение environmentName: ‘QA’, но не забывайте что это очень плохая практика если ваш код знает что-либо о сервере на котором он запущен.

Никогда не храните какую-либо секретную информацию или пароли в конфигурации окружения.

Другие настройки конфигурации такие как maxItemsOnPage или galleryAnimationSpeed должны храниться в другом месте и желательно использоваться через configuration.service.ts который может получать настройки с какого то эндпоинта или просто загружая config.json из папки assets.

1. Асинхронный подход (используйте когда конфигурация может измениться в рантайме)

// assets/config.json  {   "galleryAnimationSpeed": 5000 }  // configuration.service.ts  // ------------------------------------------------------  @Injectable({   providedIn: 'root', }) export class ConfigurationService {   private configurationSubject = new ReplaySubject<any>(1);    constructor(private httpClient: HttpClient) {     this.load();   }    // method can be used to refresh configuration   load(): void {     this.httpClient.get('/assets/config.json')       .pipe(         catchError(() => of(null)),         filter(Boolean),       )       .subscribe((configuration: any) => this.configurationSubject.next(configuration));   }    getValue(key: string, defaultValue?: any): Observable<any> {     return this.configurationSubject       .pipe(         map((configuration: any) => configuration[key] || defaultValue),       );   } }  // app.component.ts  // ------------------------------------------------------  @Component({   selector: 'app-root',   changeDetection: ChangeDetectionStrategy.OnPush,   templateUrl: './app.component.html',   styleUrls: ['./app.component.scss'] }) export class AppComponent {   galleryAnimationSpeed$: Observable<number>;    constructor(private configurationService: ConfigurationService) {     this.galleryAnimationSpeed$ = this.configurationService.getValue('galleryAnimationSpeed', 3000);      interval(10000).subscribe(() => this.configurationService.load());   } }

2. Синхронный подход (используйте когда конфигурация почти никогда не меняется)

// assets/config.json  {   "galleryAnimationSpeed": 5000 }  // configuration.service.ts  // ------------------------------------------------------  @Injectable({   providedIn: 'root', }) export class ConfigurationService {   private configuration = {};    constructor(private httpClient: HttpClient) {   }    load(): Observable<void> {     return this.httpClient.get('/assets/config.json')       .pipe(         tap((configuration: any) => this.configuration = configuration),         mapTo(undefined),       );   }    getValue(key: string, defaultValue?: any): any {     return this.configuration[key] || defaultValue;   } }  // app.module.ts  // ------------------------------------------------------  export function initApp(configurationService: ConfigurationService) {   return () => configurationService.load().toPromise(); }  @NgModule({   imports: [     BrowserModule,     HttpClientModule,     AppRoutingModule,   ],   declarations: [     AppComponent,   ],   providers: [     {       provide: APP_INITIALIZER,       useFactory: initApp,       multi: true,       deps: [ConfigurationService]     }   ],   bootstrap: [AppComponent], }) export class AppModule { }  // app.component.ts  // ------------------------------------------------------  @Component({   selector: 'app-root',   changeDetection: ChangeDetectionStrategy.OnPush,   templateUrl: './app.component.html',   styleUrls: ['./app.component.scss'] }) export class AppComponent {   galleryAnimationSpeed: number;    constructor(private configurationService: ConfigurationService) {     this.galleryAnimationSpeed = this.configurationService.getValue('galleryAnimationSpeed', 3000);   } }

Подменяйте environment переменные во время деплоя или в рантайме

Не создавайте отдельные сборки с разными конфигурациями, вместо этого используйте только одну продакшн сборку и подменяйте значения во время деплоя или во время исполнения кода. Есть несколько вариантов как сделать это:

Заменить значения плэйсхолдерами в environment файлах которые будут заменены в итоговой сборке во время деплоя

export const environment = {   production: true,   apiUrl: 'APPLICATION_API_URL', };

Во время деплоя строка APPLICATION_API_URL должна быть заменена на реальный адрес апи сервера.

Использовать глобальные переменные и инжектить конфиг файлы с помощью docker volumes

export const environment = {   production: true,   apiUrl: window.APPLICATION_API_URL, }; // in index.html before angular app bundles <script src="environment.js"></script>

Спасибо за внимание к статье, буду рад конструктивной критике и комментариям.


Также присоединяйтесь к нашему сообществу на Medium, Telegram или Twitter.


ссылка на оригинал статьи https://habr.com/ru/post/477214/

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *