Configuração de aplicativo no Angular. Melhores práticas

Como gerenciar arquivos e objetivos de configuração do ambiente


Ao criar um aplicativo angular usando as ferramentas Angular CLI ou Nrwl Nx , você sempre tem uma pasta com os arquivos de configuração do ambiente:


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

Você pode renomear environment.prod.ts para environment.production.ts, por exemplo, também pode criar arquivos de configuração adicionais, como environment.qa.ts ou environment.staging.ts .


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

O arquivo environment.ts é usado por padrão. Para usar os arquivos restantes, você deve abrir angular.json e configurar a seção fileReplacements na configuração de construção e adicionar blocos às configurações de servir e 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 criar ou executar um aplicativo com um ambiente específico, use os comandos:


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

Crie uma interface para arquivos de ambiente


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

Não use arquivos de ambiente diretamente, apenas através do DI


O uso de variáveis ​​globais e importações diretas viola a abordagem OOP e complica a testabilidade de suas classes. Portanto, é melhor criar um serviço que possa ser injetado em seus componentes e outros serviços. Aqui está um exemplo de um serviço com a capacidade de especificar um valor padrão.


 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 a configuração do ambiente e a lógica de negócios


A configuração do ambiente inclui apenas propriedades que se relacionam com o ambiente, por exemplo apiUrl . Idealmente, a configuração do ambiente deve consistir em duas propriedades:


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

Também nesta configuração, você pode adicionar uma propriedade para ativar debugMode: true mode debug ou adicionar o nome do servidor em que o aplicativo environmentName: 'QA' está em execução, mas não esqueça que essa é uma prática muito ruim se o seu código souber algo sobre o servidor no qual está executando. .


Nunca armazene informações ou senhas confidenciais em uma configuração de ambiente.


Outras definições de configuração, como maxItemsOnPage ou galleryAnimationSpeed, devem ser armazenadas em outro local e é aconselhável usar o configuration.service.ts, que pode receber configurações de algum terminal ou simplesmente carregar o config.json da pasta assets.


1. Abordagem assíncrona (use quando a configuração puder mudar no tempo de execução)


 // 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. Abordagem síncrona (use quando a configuração quase nunca muda)


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

Substituir variáveis ​​de ambiente durante a implementação ou tempo de execução


Muitas equipes violam a regra "Construa uma vez, implante várias" montando o aplicativo para cada ambiente, em vez de apenas substituir a configuração na compilação já criada.


Não crie montagens separadas com configurações diferentes; em vez disso, use apenas uma montagem de produção e substitua valores durante a implantação ou durante a execução do código. Existem várias opções para fazer isso:


Substitua os valores por espaços reservados nos arquivos de ambiente que serão substituídos na montagem final durante a implantação


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

Durante a implantação, a cadeia APPLICATION_API_URL deve ser substituída pelo endereço real do servidor api.


Use variáveis ​​globais e injete arquivos de configuração com volumes docker


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

Obrigado por sua atenção ao artigo, terei prazer em críticas e comentários construtivos.




Entre também para a nossa comunidade no Medium , Telegram ou Twitter .

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


All Articles