Przeniesienie aplikacji Angular z lokalnego komputera na testy, staging i produkcję często ujawnia problem z adresami API, flagami funkcji oraz ustawieniami logowania. Temat angular environment variables dotyczy właśnie bezpiecznego i przewidywalnego zarządzania taką konfiguracją. Pokażę, jak działają pliki środowiskowe, kiedy użyć konfiguracji budowania, a kiedy lepiej wczytać ustawienia dopiero po uruchomieniu aplikacji.
Najważniejsze zasady konfiguracji Angulara w różnych środowiskach
- Pliki environment pozwalają podmieniać ustawienia podczas budowania aplikacji.
- environment.ts w aktualnym układzie Angular CLI pełni rolę bazowej konfiguracji produkcyjnej.
- Adres API nie jest sekretem, ponieważ trafia do kodu JavaScript dostępnego w przeglądarce.
- Konfiguracja runtime sprawdza się wtedy, gdy ten sam build ma działać w wielu środowiskach.
- Pliki .env nie są natywnym mechanizmem Angulara i wymagają dodatkowego narzędzia lub skryptu.
Co w Angularze oznacza konfiguracja środowiskowa
Aplikacja zwykle potrzebuje innych ustawień lokalnie, innych na środowisku testowym i jeszcze innych na produkcji. Najczęściej zmieniają się adres API, poziom logowania, przełączniki funkcji, identyfikator aplikacji analitycznej albo adres usługi uwierzytelniania.
Angular CLI rozwiązuje ten problem głównie przez nazwane konfiguracje budowania oraz podmianę plików. Podczas kompilacji narzędzie wybiera właściwy wariant, dzięki czemu kod aplikacji może korzystać z jednego importu, a wartości zostaną dobrane na podstawie użytej konfiguracji.
To ważne rozróżnienie. Nie chodzi o zmienną systemową odczytywaną przez przeglądarkę w czasie działania. W klasycznym podejściu wartość jest wpisywana do gotowego bundla podczas builda, dlatego zmiana konfiguracji po wdrożeniu wymaga ponownego zbudowania aplikacji.
Pliki environment i file replacements krok po kroku
W projekcie, który nie ma jeszcze katalogu środowiskowego, można wygenerować go poleceniem:
ng generate environmentsAngular CLI utworzy katalog podobny do poniższego:
src/environments/
├── environment.ts
├── environment.development.ts
└── environment.staging.tsW aktualnym układzie bazowy plik environment.ts zawiera domyślne ustawienia produkcyjne. Przykład konfiguracji może wyglądać tak:
export const environment = {
production: true,
apiUrl: 'https://api.example.pl',
enableDebugLogs: false
};Dla środowiska deweloperskiego przygotowuję osobny wariant:
export const environment = {
production: false,
apiUrl: 'http://localhost:5000',
enableDebugLogs: true
};W kodzie aplikacji importuję zawsze plik bazowy, a nie plik z nazwą konkretnego środowiska:
import { environment } from '../environments/environment';
fetch(`${environment.apiUrl}/products`);O tym, który plik zostanie użyty, decyduje sekcja fileReplacements w pliku angular.json. Dla konfiguracji deweloperskiej wygląda ona następująco:
{
"configurations": {
"development": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.development.ts"
}
]
}
}
}Budowanie konkretnego wariantu uruchamiam poleceniem:
ng build --configuration development
ng build --configuration staging
ng buildOstatnie polecenie korzysta z konfiguracji domyślnej. Z kolei ng serve zazwyczaj uruchamia wariant deweloperski. Częsty błąd polega na założeniu, że sama nazwa pliku wystarczy. Angular nie wybiera automatycznie każdego nowego pliku, dlatego nową konfigurację trzeba powiązać z buildem w angular.json.
Jak wybrać między build time i runtime
Pliki środowiskowe są wygodne, ale nie pasują do każdego modelu wdrożenia. Jeśli dla każdej wersji aplikacji tworzę osobny build, podmiana plików jest prosta i dobrze współgra z CI/CD. Jeśli jednak chcę zbudować obraz Dockera tylko raz i uruchamiać go w kilku środowiskach, lepsza będzie konfiguracja ładowana po starcie.
| Metoda | Kiedy używać | Najważniejsze ograniczenie |
|---|---|---|
| Pliki environment | Osobny build dla development, staging i production | Zmiana wartości wymaga ponownego builda |
| Opcja define | Pojedyncze wartości przekazywane do builda z pipeline | Wymaga nowego buildera aplikacji i odpowiednich deklaracji TypeScript |
| Plik config.json ładowany runtime | Jeden artefakt wdrażany w wielu środowiskach | Aplikacja musi zaczekać na wczytanie konfiguracji |
Nowszy builder aplikacji Angulara udostępnia również opcję define, która pozwala zastępować identyfikatory podczas budowania. Może być przydatna dla wartości takich jak numer commita albo host API przekazywany z pipeline'u:
ng build --define API_RETRY=3Przy tym podejściu TypeScript powinien znać typ używanego identyfikatora, na przykład dzięki deklaracji declare const API_RETRY: number;. Ja traktuję tę technikę jako dobre rozwiązanie dla kilku prostych stałych, ale przy większej konfiguracji wolę czytelny obiekt środowiskowy.
Konfiguracja runtime dla jednego buildu
Jeśli aplikacja ma działać w kilku wdrożeniach bez przebudowywania, można trzymać ustawienia w publicznym pliku, na przykład assets/config.json:
{
"apiUrl": "https://api.example.pl",
"enableDebugLogs": false
}Serwis konfiguracyjny pobiera plik przed uruchomieniem interfejsu. W aplikacji standalone można wykorzystać funkcję inicjalizującą, która wykona żądanie jeszcze przed wyrenderowaniem głównego widoku. Najważniejsza zasada brzmi nie uruchamiaj logiki zależnej od konfiguracji przed zakończeniem ładowania pliku.
W praktyce pipeline może podczas wdrożenia wygenerować zawartość config.json z parametrów Azure App Service, kontenera albo innego systemu CI/CD. Dzięki temu ten sam katalog z plikami statycznymi trafia na testy i produkcję, a różni się tylko zewnętrzna konfiguracja.
To podejście ma cenę. Dochodzi obsługa błędu pobierania, walidacja schematu i stan oczekiwania podczas startu. Przy małej aplikacji może to być niepotrzebne komplikowanie projektu. Przy wielu środowiskach oraz częstych zmianach adresów usług zwykle szybko się zwraca.
Dlaczego pliki environment nie służą do przechowywania sekretów
Najważniejsza rzecz, o której łatwo zapomnieć, jest prosta: wszystko, co trafi do aplikacji Angular, może zostać odczytane przez użytkownika. Pliki środowiskowe są kompilowane do kodu klienta, więc nie wolno umieszczać w nich haseł, prywatnych kluczy ani sekretów Azure.
Publiczny adres API, nazwa środowiska czy identyfikator klienta przeznaczony do użycia w przeglądarce nie muszą być tajne. Sekretem pozostaje natomiast klucz pozwalający ominąć autoryzację albo wykonać uprzywilejowaną operację.
W aplikacji opartej na Angularze tokeny i sekrety powinny pozostać po stronie backendu. Dla projektu .NET sensowny układ to przechowywanie poufnych wartości w konfiguracji API, Azure Key Vault albo bezpiecznych zmiennych pipeline'u. Frontend komunikuje się z backendem, ale nie otrzymuje jego poświadczeń.
Dobrym testem jest pytanie, czy zaakceptowałbym publikację danej wartości w kodzie strony. Jeśli odpowiedź brzmi „nie”, nie powinna znaleźć się ani w environment.ts, ani w config.json, ani w parametrze define.
Najczęstsze błędy przy konfiguracji Angulara
Importowanie niewłaściwego pliku
Komponenty powinny importować bazową ścieżkę environment, a nie bezpośrednio environment.development. Bezpośredni import omija mechanizm podmiany i może sprawić, że produkcyjny build nadal będzie korzystał z ustawień lokalnych.
Oczekiwanie działania process.env w przeglądarce
Angular działa po stronie klienta, a przeglądarka nie ma bezpośredniego dostępu do zmiennych środowiskowych serwera. Biblioteki obsługujące pliki .env mogą wygenerować konfigurację podczas builda, ale nie tworzą magicznego połączenia z systemem operacyjnym po wdrożeniu.
Brak walidacji adresu API
Literówka w adresie backendu często wychodzi dopiero po wdrożeniu. Warto sprawdzać konfigurację w pipeline oraz wykonać prosty test health check dla każdego środowiska. Przy konfiguracji runtime dobrze walidować nie tylko obecność pola, ale też jego typ i poprawny format URL.
Różne nazwy pól w plikach
Jeżeli wariant lokalny ma pole apiUrl, a staging używa przypadkiem apiURL, problem może pojawić się dopiero w konkretnym buildzie. Pomaga wspólny interfejs TypeScript albo funkcja tworząca konfigurację, dzięki której każdy wariant musi dostarczyć ten sam zestaw wartości.
Przeczytaj również: API w .NET od podstaw - od endpointu do wdrożenia
Traktowanie flagi production jako mechanizmu bezpieczeństwa
Wartość production: true może sterować logowaniem, ale nie zastępuje autoryzacji, kontroli dostępu ani ochrony endpointów. Użytkownik może zmodyfikować kod klienta, dlatego każde zabezpieczenie musi być egzekwowane na serwerze.
Praktyczny model dla aplikacji Angular i Azure
Dla typowej aplikacji biznesowej korzystającej z API .NET przyjąłbym trzy konfiguracje: development, staging i production. W małym projekcie wystarczą pliki environment oraz osobny build w pipeline. To rozwiązanie jest czytelne, łatwe do debugowania i nie wymaga dodatkowej infrastruktury.
W większym systemie rozważyłbym plik runtime, szczególnie gdy jeden obraz kontenera ma być promowany między środowiskami. Sekrety przechowywałbym po stronie API lub w menedżerze sekretów, a frontendowi udostępniałbym wyłącznie publiczne ustawienia, takie jak adres usługi, nazwa regionu czy dozwolone przełączniki funkcji.
Przed wdrożeniem sprawdzam cztery rzeczy:
- czy wybrana konfiguracja builda jest jawnie podana w pipeline,
- czy aplikacja odwołuje się do właściwego pliku bazowego,
- czy w bundlu nie ma sekretów ani danych testowych,
- czy adres API odpowiada środowisku, na które wdrażam aplikację.
Taki prosty przegląd często daje więcej niż rozbudowane abstrakcje konfiguracyjne. Najlepsze rozwiązanie to nie to z największą liczbą plików, tylko takie, w którym wiadomo kiedy wartość jest wybierana, kto nią zarządza i czy może być publiczna.
Od environment.ts do świadomego modelu wdrożeń
Pliki środowiskowe Angulara są dobrym punktem startowym dla konfiguracji zależnej od buildu. Zapewniają prosty podział na development, staging i production, a mechanizm file replacements pozwala utrzymać jeden sposób importowania ustawień w całej aplikacji.
Gdy wymagania rosną, można przejść do wartości przekazywanych przez define albo konfiguracji ładowanej podczas uruchamiania. Niezależnie od wybranej metody trzeba pamiętać o jednej granicy: kod dostarczony do przeglądarki jest publiczny, więc prawdziwe sekrety zawsze powinny pozostać po stronie serwera.
