Configuración de la aplicación en Angular. Mejores prácticas

Cómo administrar los archivos y objetivos de configuración del entorno


Cuando creaste una aplicación angular usando las herramientas Angular CLI o Nrwl Nx , siempre tienes una carpeta con archivos de configuración del entorno:


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

Puede cambiar el nombre de environment.prod.ts a environment.production.ts, por ejemplo, también puede crear archivos de configuración adicionales como environment.qa.ts o environment.staging.ts .


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

El archivo environment.ts se usa de manera predeterminada. Para usar los archivos restantes, debe abrir angular.json y configurar la sección fileReplacements en la configuración de compilación y agregar bloques a las configuraciones de servidor y 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" } } } } } 

Para compilar o ejecutar una aplicación con un entorno específico, use los comandos:


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

Crear una interfaz para archivos de entorno.


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

No use archivos de entorno directamente, solo a través de DI


El uso de variables globales e importaciones directas viola el enfoque OOP y complica la capacidad de prueba de sus clases. Por lo tanto, es mejor crear un servicio que pueda inyectarse en sus componentes y otros servicios. Aquí hay un ejemplo de dicho servicio con la capacidad de especificar un valor predeterminado.


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

Separe la configuración de su entorno y la lógica empresarial


La configuración del entorno incluye solo propiedades relacionadas con el entorno, por ejemplo apiUrl . Idealmente, la configuración del entorno debe constar de dos propiedades:


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

También en esta configuración, puede agregar una propiedad para habilitar debugMode: depuración en modo verdadero o puede agregar el nombre del servidor donde se ejecuta el entorno de aplicación Nombre : 'QA' , pero no olvide que esta es una práctica muy mala si su código sabe algo sobre el servidor en el que se está ejecutando .


Nunca almacene información confidencial o contraseñas en una configuración de entorno.


Otras configuraciones como maxItemsOnPage o galleryAnimationSpeed deben almacenarse en otro lugar y es aconsejable usar configuration.service.ts, que puede recibir configuraciones desde algún punto final o simplemente cargar config.json desde la carpeta de activos.


1. Enfoque asincrónico (usar cuando la configuración puede cambiar en tiempo de ejecución)


 // 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. Enfoque sincrónico (se usa cuando la configuración casi nunca cambia)


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

Reemplazar variables de entorno durante la implementación o el tiempo de ejecución


Muchos equipos violan la regla "Compilar una vez, implementar muchos" al ensamblar la aplicación para cada entorno en lugar de simplemente cambiar la configuración en la compilación ya construida.


No cree conjuntos separados con diferentes configuraciones; en su lugar, use solo un conjunto de producción y sustituya los valores durante la implementación o durante la ejecución del código. Hay varias opciones para hacer esto:


Reemplace los valores con marcadores de posición en los archivos de entorno que se reemplazarán en el ensamblaje final durante la implementación


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

Durante la implementación, la cadena APPLICATION_API_URL debe reemplazarse con la dirección real del servidor api.


Use variables globales e inyecte archivos de configuración con volúmenes de docker


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

Gracias por su atención al artículo, me complacerá recibir críticas y comentarios constructivos.




También únete a nuestra comunidad en Medium , Telegram o Twitter .

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


All Articles