Egy modern webalkalmazás fejlesztése során szinte elkerülhetetlen, hogy különböző környezetekben eltérő beállításokra legyen szükségünk. Gondoljunk csak a fejlesztői, teszt (staging) és éles (production) környezetekre. Mindegyiknek más API végpontja, más adatbázis-kapcsolata vagy éppen eltérő konfigurációs paraméterei lehetnek. Az Angular környezeti változók kezelése kulcsfontosságú ahhoz, hogy alkalmazásaink rugalmasak, biztonságosak és könnyen telepíthetőek legyenek, függetlenül attól, hogy melyik környezetben futnak.
Ebben az átfogó cikkben mélyrehatóan tárgyaljuk, hogyan kezelhetjük hatékonyan a környezeti változókat Angular alkalmazásainkban. Megvizsgáljuk az alapvető, beépített megoldásokat, bemutatjuk, hogyan hozhatunk létre egyedi környezeteket, és rávilágítunk a build-time és runtime változók közötti különbségekre. Ezen felül haladó technikákat is bemutatunk a rugalmas runtime konfigurációkhoz, foglalkozunk a biztonsági aspektusokkal, és megmutatjuk, hogyan illeszthetjük be a környezeti változók kezelését CI/CD pipeline-unkba és Docker konténereinkbe.
Miért olyan fontosak a környezeti változók?
Képzeljük el, hogy alkalmazásunk egy időjárás-előrejelző API-t használ. Fejlesztés közben talán egy ingyenes, teszt API kulcsot használunk, ami korlátozott kérésszámmal rendelkezik. Amikor az alkalmazás éles környezetbe kerül, szükségünk lesz egy fizetős, nagy teljesítményű API kulcsra. Ezen felül, a fejlesztői szerverünk lehet a http://localhost:3000/api
címen, míg az éles szerver a https://api.valosdomain.com/v1
címen. Ahhoz, hogy ne kelljen manuálisan átírni a kódot minden telepítés előtt, és elkerüljük a hibákat, pontosan erre valók a környezeti változók.
A környezeti változók lehetővé teszik számunkra, hogy:
- Különböző API végpontokat, adatbázis-URL-eket és más szolgáltatások címét használjuk.
- Különböző API kulcsokat és titkokat kezeljünk (biztonsági megfontolásokkal).
- Kapcsoljunk be vagy ki funkciókat (pl. debug mód, feature flag-ek).
- Különböző loggolási szintet állítsunk be.
Az Angular beépített megoldása: az environment.ts
fájlok
Az Angular CLI alapértelmezetten beépített támogatást nyújt a környezeti változók kezeléséhez, mégpedig az environment.ts
fájlokon keresztül. Amikor egy új Angular projektet hozunk létre, a src/environments/
mappában két fájlt találunk:
environment.ts
: Ez a fájl tartalmazza a fejlesztői környezet alapértelmezett beállításait.environment.prod.ts
: Ez a fájl tartalmazza az éles (production) környezet beállításait.
// src/environments/environment.ts
export const environment = {
production: false,
apiUrl: 'http://localhost:3000/api',
debugMode: true
};
// src/environments/environment.prod.ts
export const environment = {
production: true,
apiUrl: 'https://api.valosdomain.com/v1',
debugMode: false
};
Hogyan működik az angular.json
fájlban lévő konfigurációval?
Az Angular CLI a angular.json
fájl segítségével határozza meg, hogy melyik environment.ts
fájlt használja a fordítás során. A build
szekcióban, a configurations
alatt találjuk meg a beállításokat. Például az éles konfiguráció a fileReplacements
kulcsot használja:
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
// ...
},
"configurations": {
"production": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
// ...
}
}
}
}
Amikor lefuttatjuk az ng build
parancsot, az environment.ts
tartalmát használja. Ha azonban az ng build --configuration=production
(vagy röviden ng build --prod
régebbi Angular verziókban) parancsot adjuk ki, az Angular CLI automatikusan kicseréli a src/environments/environment.ts
fájlt a src/environments/environment.prod.ts
fájl tartalmával a fordítás (build) folyamata során.
Változók elérése komponensekben és szolgáltatásokban
A környezeti változók elérése rendkívül egyszerű. Csak importálni kell az environment
objektumot, és máris használhatjuk a benne lévő értékeket:
import { Component } from '@angular/core';
import { environment } from '../environments/environment'; // Fontos: a relatív útvonalra figyelni!
@Component({
selector: 'app-root',
template: `
<h1>Alkalmazás állapota</h1>
<p>Éles mód: {{ isProduction }}</p>
<p>API URL: {{ apiUrl }}</p>
`
})
export class AppComponent {
isProduction = environment.production;
apiUrl = environment.apiUrl;
constructor() {
if (environment.debugMode) {
console.log('Debug mód bekapcsolva!');
}
}
}
Egyedi környezetek létrehozása (pl. staging, QA)
A fejlesztés és az éles környezet mellett gyakran van szükségünk további környezetekre, például teszt (staging) vagy minőségbiztosítási (QA) célokra. Ezeket könnyedén létrehozhatjuk:
- Új környezeti fájl létrehozása: Hozzunk létre egy új fájlt, például
src/environments/environment.staging.ts
néven, és töltsük fel a megfelelő beállításokkal:// src/environments/environment.staging.ts export const environment = { production: false, // Vagy true, attól függ, hogyan kezeljük a staging környezetet apiUrl: 'https://staging.api.valosdomain.com/v1', debugMode: true, envName: 'Staging' };
- Az
angular.json
fájl frissítése: Hozzá kell adnunk egy új konfigurációt abuild
és aserve
szekciókhoz azangular.json
fájlban."architect": { "build": { "builder": "@angular-devkit/build-angular:browser", "options": { /* ... */ }, "configurations": { "production": { /* ... */ }, "staging": { // Új staging konfiguráció "fileReplacements": [ { "replace": "src/environments/environment.ts", "with": "src/environments/environment.staging.ts" } ], "optimization": true, "outputHashing": "all", "sourceMap": false, "namedChunks": false, "extractLicenses": true, "vendorChunk": false, "buildOptimizer": true, "budgets": [ { "type": "initial", "maximumWarning": "500kb", "maximumError": "1mb" }, { "type": "anyComponentStyle", "maximumWarning": "2kb", "maximumError": "4kb" } ] } } }, "serve": { "builder": "@angular-devkit/build-angular:dev-server", "options": { /* ... */ }, "configurations": { "production": { /* ... */ }, "staging": { // Új staging serve konfiguráció "browserTarget": "your-app-name:build:staging" } } } }
- Alkalmazás fordítása egyedi környezettel: Most már fordíthatjuk az alkalmazást a
staging
környezeti beállításokkal:ng build --configuration=staging ng serve --configuration=staging
A build-time és runtime változók különbsége: Mikor melyiket használjuk?
Az eddig bemutatott megoldás az ún. build-time változók kategóriájába tartozik. Ez azt jelenti, hogy a környezeti változók értékei a fordítás (build) során beágyazódnak az alkalmazás JavaScript kódjába. Ennek megvan az előnye és hátránya is:
- Előnyök: Egyszerű megvalósítás, gyors hozzáférés, nincs szükség különleges szerveroldali beállításra.
- Hátrányok: Minden egyes környezetváltozás esetén újra kell fordítani és újra kell telepíteni az alkalmazást. Ez nem ideális, ha egyetlen buildet szeretnénk telepíteni több, eltérő környezetre (pl. Docker konténerben), vagy ha a környezeti beállítások gyakran változnak.
Ezzel szemben a runtime változók az alkalmazás indulásakor, futásidőben kerülnek betöltésre. Ez sokkal rugalmasabb megközelítést tesz lehetővé, mivel egyetlen, egyszer lefordított alkalmazáscsomagot (buildet) telepíthetünk különböző környezetekbe, és a konfigurációt futásidőben injektálhatjuk.
Haladó technikák runtime változók kezelésére
Amikor a build-time konfiguráció nem elegendő, a runtime megoldások jöhetnek szóba. Nézzünk meg néhány elterjedt módszert:
1. Konfigurációs JSON fájl betöltése az assets
mappából
Ez egy viszonylag egyszerű és elterjedt módszer. Létrehozunk egy config.json
fájlt az alkalmazás assets
mappájában, amit az alkalmazás indulásakor olvasunk be. A config.json
tartalma különbözhet a különböző környezetekben.
// src/assets/config.json (fejlesztéskor ideiglenesen)
{
"apiUrl": "http://localhost:3000/api",
"featureEnabled": true
}
Telepítéskor ezt a fájlt a célkörnyezetnek megfelelően kicseréljük (pl. CI/CD pipeline-ban vagy Docker image-ben).
Az Angular APP_INITIALIZER
tokenje segítségével biztosíthatjuk, hogy a konfiguráció betöltése még az alkalmazás indulása előtt megtörténjen, így az összes komponens és szolgáltatás hozzáférhet a konfigurációs adatokhoz.
// src/app/config.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
@Injectable({
providedIn: 'root'
})
export class ConfigService {
private appConfig: any;
constructor(private http: HttpClient) { }
loadConfig(): Promise<any> {
return this.http.get('/assets/config.json')
.toPromise()
.then(data => {
this.appConfig = data;
});
}
get(key: string): any {
return this.appConfig[key];
}
}
// src/app/app.module.ts
import { NgModule, APP_INITIALIZER } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { HttpClientModule } from '@angular/common/http';
import { AppComponent } from './app.component';
import { ConfigService } from './config.service';
export function initializeApp(configService: ConfigService) {
return () => configService.loadConfig();
}
@NgModule({
declarations: [AppComponent],
imports: [
BrowserModule,
HttpClientModule
],
providers: [
ConfigService,
{
provide: APP_INITIALIZER,
useFactory: initializeApp,
deps: [ConfigService],
multi: true
}
],
bootstrap: [AppComponent]
})
export class AppModule { }
Előnyök: Rugalmas, egyetlen build több környezetben is futhat. Könnyen integrálható CI/CD-be.
Hátrányok: A config.json
fájlnak nyilvánosan elérhetőnek kell lennie. Ha az alkalmazás nem tudja betölteni a konfigurációt, az hibát okozhat az indulásnál.
2. Backend API végpont használata
Egy még robusztusabb megoldás, ha a konfigurációs adatokat egy dedikált backend API végpontról kérjük le. Ez lehetővé teszi, hogy a konfiguráció teljesen dinamikus legyen, akár az adatbázisból vagy más szerveroldali konfigurációkezelőből származva.
// src/app/config.service.ts (módosított változat)
// ...
loadConfig(): Promise<any> {
// Ezt az URL-t beállíthatjuk egy environment.ts fájlban (build-time változó)
// vagy egy placeholderrel (lásd később).
return this.http.get('/api/config')
.toPromise()
.then(data => {
this.appConfig = data;
});
}
// ...
Előnyök: Maximális rugalmasság, a konfiguráció szerveroldalon kezelhető és akár dinamikusan is változhat. Biztonságosabb, mivel a konfigurációs adatok nincsenek közvetlenül a kliens oldalon tárolva.
Hátrányok: Szükség van egy dedikált backend végpontra. Az alkalmazás nem tud elindulni, ha a backend nem elérhető.
3. Server-side injektálás (pl. Nginx, Docker)
Ez a módszer magában foglalja a környezeti változók injektálását a már lefordított (buildelt) Angular alkalmazásba egy szerveroldali folyamat során, például egy Nginx webkiszolgáló vagy egy Docker konténer indításakor.
Elv: Az Angular alkalmazásban helyőrzőket (placeholders) használunk (pl. window.APP_CONFIG.API_URL
vagy akár egyszerű stringeket __API_URL__
). A szerver (Nginx) vagy a konténer indító szkriptje ezeket a helyőrzőket cseréli ki a környezeti változók értékeivel.
<!-- index.html -->
<script>
window.APP_CONFIG = {
apiUrl: "__API_URL__",
featureFlag: "__FEATURE_FLAG__"
};
</script>
<app-root></app-root>
A TypeScript kódban ezt a globális objektumot használhatjuk:
declare global {
interface Window {
APP_CONFIG: {
apiUrl: string;
featureFlag: boolean;
};
}
}
// ... komponensben vagy szolgáltatásban
const apiUrl = window.APP_CONFIG.apiUrl;
Egy Nginx konfigurációban a sub_filter
direktíva használható a csere elvégzésére:
server {
listen 80;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
sub_filter '__API_URL__' "$API_URL";
sub_filter '__FEATURE_FLAG__' "$FEATURE_FLAG";
sub_filter_once off; # Többszöri csere engedélyezése
try_files $uri $uri/ /index.html;
}
}
A $API_URL
és $FEATURE_FLAG
értékeket az Nginx futtatásakor állítjuk be környezeti változókként. Docker esetében egy belépési szkript (entrypoint script) végezheti el a fájlon belüli cserét a konténer indításakor.
Előnyök: Maximális rugalmasság, nincs szükség újrafordításra, a konfiguráció közvetlenül a szerver/konténer környezetéből származik. Nagyon jól működik Dockerrel és konténerizált alkalmazásokkal.
Hátrányok: Bonyolultabb beállítás, függ a szerver/konténer környezetétől. A helyőrzők véletlenül is előfordulhatnak a kódban, ami hibás cserékhez vezethet.
Típusbiztonság és karbantarthatóság
Akár build-time, akár runtime változókat használunk, érdemes típusbiztonságot adni nekik egy interfésszel. Ez segít a fordítási idejű hibakeresésben és javítja a kód olvashatóságát.
// src/app/app-config.interface.ts
export interface AppConfig {
production: boolean;
apiUrl: string;
debugMode: boolean;
envName?: string; // Opcionális, ha nem minden környezetben van
}
// src/environments/environment.ts (és társai)
import { AppConfig } from '../app/app-config.interface';
export const environment: AppConfig = {
production: false,
apiUrl: 'http://localhost:3000/api',
debugMode: true,
envName: 'Development'
};
A ConfigService
esetében is érdemes az appConfig
property-nek adni a AppConfig
típust.
Biztonsági megfontolások: Mit NE tároljunk az environment
fájlokban?
Ez az egyik legfontosabb szempont! Soha, ismétlem, soha ne tároljunk szenzitív adatokat (pl. adatbázis jelszavakat, privát API kulcsokat, titkosítatlan titkokat) az Angular alkalmazás environment.ts
fájljaiban vagy bármilyen kliensoldali kódban! Az Angular alkalmazás lefordított JavaScript kódja könnyedén megtekinthető a böngésző fejlesztői eszközei segítségével. Bármi, ami ott van, az mindenki számára hozzáférhetővé válik.
A szenzitív adatok kezelésére mindig szerveroldali megoldásokat használjunk:
- Backend proxy: Az Angular alkalmazás a saját backendjét hívja meg, ami aztán továbbítja a kérést a tényleges (pl. harmadik féltől származó) API-nak, a szükséges titkokat a szerveroldalon hozzáadva.
- Szerveroldali konfigurációkezelő: A titkokat egy dedikált titokkezelő szolgáltatásban tároljuk (pl. AWS Secrets Manager, HashiCorp Vault), és csak a backend fér hozzájuk.
- JWT tokenek és OAuth: Felhasználói hitelesítés esetén soha ne tároljunk jelszavakat, hanem token alapú hitelesítést használjunk, és a tokeneket biztonságosan tároljuk (pl.
localStorage
vagysessionStorage
, bár itt is vannak biztonsági megfontolások).
Környezeti változók használata Dockerrel és CI/CD-vel
A konténerizáció és az automatizált build/telepítési folyamatok (CI/CD) során a környezeti változók kezelése különösen fontossá válik.
- Docker: Ha Docker konténerbe csomagoljuk az Angular alkalmazásunkat, gyakran egyetlen image-et szeretnénk létrehozni, amit aztán különböző környezetekben különböző konfigurációval futtatunk. Ekkor a runtime változók (különösen a JSON fájl csere vagy a server-side injektálás) a legmegfelelőbbek. A Dockerfile tartalmazhatja a buildet, az indító szkript pedig a konfiguráció cseréjét.
# Dockerfile # ... FROM nginx:alpine COPY nginx.conf /etc/nginx/conf.d/default.conf COPY dist/your-app-name /usr/share/nginx/html COPY docker-entrypoint.sh /docker-entrypoint.sh RUN chmod +x /docker-entrypoint.sh ENTRYPOINT ["/docker-entrypoint.sh"] CMD ["nginx", "-g", "daemon off;"]
A
docker-entrypoint.sh
fájl futásidőben módosíthatja azindex.html
-t vagy aconfig.json
-t a Docker környezeti változók alapján. - CI/CD pipeline: Egy automatizált pipeline (pl. GitLab CI, Jenkins, Azure DevOps) könnyedén kezelheti a különböző környezetek buildjét.
- Build-time változók esetén: A pipeline minden egyes környezethez (dev, staging, prod) külön build lépést futtat, a megfelelő
--configuration
paraméterrel (pl.ng build --configuration=production
). Ezután az adott buildet telepíti a célkörnyezetbe. - Runtime változók esetén: A pipeline csak egyetlen buildet hoz létre (pl.
ng build
). Telepítéskor a célkörnyezetnek megfelelőconfig.json
fájlt injektálja a buildbe, vagy a szerver konfigurációját (Nginx) frissíti a környezeti változókkal. A szenzitív adatok (pl. API kulcsok) ilyenkor a CI/CD rendszerben tárolt titkokból (secrets) kerülnek kinyerésre.
- Build-time változók esetén: A pipeline minden egyes környezethez (dev, staging, prod) külön build lépést futtat, a megfelelő
Gyakori hibák és tippek
- Ne felejtsd el az
angular.json
-t: Ha új környezetet hozol létre, győződj meg róla, hogy azangular.json
fájlban is beállítottad a megfelelőfileReplacements
ésserve
konfigurációkat. - Rossz `–configuration` paraméter: Győződj meg róla, hogy a megfelelő konfigurációs paraméterrel indítod a buildet (pl.
ng build --configuration=staging
). - Szenzitív adatok Git-re töltése: Nagyon gyakori hiba. A
.gitignore
fájlba soha ne tegyél fel olyan fájlt, ami szenzitív adatokat tartalmazhat, vagy használd a runtime konfigurációt, ami kizárja a titkok frontendbe kerülését. - Túl sok környezet: Próbáld meg minimalizálni a környezetek számát. Néha a dev, staging, prod trió is elegendő, és a finomhangolásokat a runtime konfigurációval érdemes megoldani.
- Környezetfüggő logika kezelése: Ha a logikádnak környezetfüggőnek kell lennie, használd az
environment.production
változót. Például:if (environment.production) { // Éles módú analytics kód } else { // Fejlesztői loggolás }
Összegzés
Az Angular alkalmazások környezeti változóinak kezelése kritikus a modern fejlesztési munkafolyamatokban. Az environment.ts
fájlok beépített támogatást nyújtanak a build-time konfigurációhoz, ami sok esetben elegendő. Azonban a nagyobb, komplexebb projektek, a konténerizáció és a CI/CD igényei gyakran megkövetelik a rugalmasabb runtime konfigurációs megközelítéseket, mint például a JSON fájlok, API végpontok vagy szerveroldali injektálás használatát.
A legfontosabb tanulság, amit magaddal kell vinned, az a biztonság: soha ne tárolj érzékeny adatokat a kliensoldali kódban! Mindig válaszd a projekt igényeinek és a csapat képességeinek legmegfelelőbb megoldást, figyelembe véve a rugalmasságot, karbantarthatóságot és a biztonságot. Egy jól megtervezett környezeti változó stratégia hosszú távon rengeteg időt és fejfájást spórolhat meg számodra.
Leave a Reply