Configuration d'application sur Angular. Meilleures pratiques

Comment gérer les fichiers de configuration de l'environnement et les objectifs


Lorsque vous avez créé une application angulaire à l'aide des outils CLI angulaire ou Nrwl Nx , vous disposez toujours d'un dossier avec des fichiers de configuration d'environnement:


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

Vous pouvez renommer environment.prod.ts en environment.production.ts par exemple, vous pouvez également créer des fichiers de configuration supplémentaires tels que environment.qa.ts ou environment.staging.ts .


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

Le fichier environment.ts est utilisé par défaut. Pour utiliser les fichiers restants, vous devez ouvrir angular.json et configurer la section fileReplacements dans la configuration de construction et ajouter des blocs aux configurations serve et e2e .


 { "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" } } } } } 

Pour créer ou exécuter une application avec un environnement spécifique, utilisez les commandes:


 ng build --configuration=staging ng start --configuration=staging ng e2e --configuration=staging  ng build --prod     ng build --configuration=production 

Créer une interface pour les fichiers d'environnement


 // environment-interface.ts export interface EnvironmentInterface { production: boolean; apiUrl: string; } // environment.ts export const environment: EnvironmentInterface = { production: false, apiUrl: 'https://api.url', }; 

N'utilisez pas les fichiers d'environnement directement, uniquement via DI


L'utilisation de variables globales et d'importations directes viole l'approche OOP et complique la testabilité de vos classes. Par conséquent, il est préférable de créer un service qui peut être injecté dans vos composants et autres services. Voici un exemple d'un tel service avec la possibilité de spécifier une valeur par défaut.


 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 { } 

Séparez la configuration de votre environnement et la logique métier


La configuration de l'environnement comprend uniquement les propriétés liées à l'environnement, par exemple apiUrl . Idéalement, la configuration de l'environnement doit comprendre deux propriétés:


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

Dans cette configuration, vous pouvez également ajouter une propriété pour activer debugMode: débogage en mode vrai ou vous pouvez ajouter le nom du serveur sur lequel l'application environmentName: 'QA' est en cours d'exécution, mais n'oubliez pas que c'est une très mauvaise pratique si votre code sait quelque chose sur le serveur sur lequel il s'exécute. .


Ne stockez jamais d'informations ou de mots de passe sensibles dans une configuration d'environnement.


Les autres paramètres de configuration tels que maxItemsOnPage ou galleryAnimationSpeed doivent être stockés à un autre endroit et il est conseillé d'utiliser configuration.service.ts, qui peut recevoir les paramètres d'un certain point de terminaison ou simplement charger config.json à partir du dossier d'actifs.


1. Approche asynchrone (à utiliser lorsque la configuration peut changer lors de l'exécution)


 // 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. Approche synchrone (Ă  utiliser lorsque la configuration ne change presque jamais)


 // 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); } } 

Remplacer les variables d'environnement pendant le déploiement ou l'exécution


De nombreuses équipes enfreignent la règle «Générer une fois, déployer plusieurs» en assemblant l'application pour chaque environnement au lieu de simplement modifier la configuration dans la version déjà créée.


Ne créez pas d'assemblys séparés avec des configurations différentes; utilisez plutôt un seul assembly de production et substituez des valeurs pendant le déploiement ou pendant l'exécution de code. Il existe plusieurs options pour ce faire:


Remplacez les valeurs par des espaces réservés dans les fichiers d'environnement qui seront remplacés dans l'assemblage final pendant le déploiement


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

Pendant le déploiement, la chaîne APPLICATION_API_URL doit être remplacée par l'adresse réelle du serveur api.


Utilisez des variables globales et injectez des fichiers de configuration avec des volumes de docker


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

Merci pour votre attention Ă  l'article, je serai heureux de critiques constructives et commentaires.




Rejoignez également notre communauté sur Medium , Telegram ou Twitter .

Source: https://habr.com/ru/post/fr477214/


All Articles