Angular Environments — environment.ts, Build Configurations and API URLs

Angular’s environment system provides build-time configuration — different values for development, staging, and production builds. The API URL, feature flags, analytics IDs, and debug settings all live in environment files. The correct approach in Angular 18 is to inject environment values via a typed InjectionToken rather than importing the environment file directly in services — this makes services testable (no file system dependency) and follows the dependency injection pattern consistently.

Angular Environment Configuration

// ── src/environments/environment.ts — development ─────────────────────────
export const environment = {
  production:      false,
  apiUrl:          'http://localhost:5000',    // ASP.NET Core API
  enableDebugTools: true,
  appVersion:      '1.0.0-dev',
  features: {
    comments:   true,
    darkMode:   true,
    analytics:  false,
  }
};

// ── src/environments/environment.prod.ts — production ─────────────────────
export const environment = {
  production:      true,
  apiUrl:          'https://api.blogapp.com',  // production API
  enableDebugTools: false,
  appVersion:      '1.0.0',
  features: {
    comments:   true,
    darkMode:   true,
    analytics:  true,
  }
};

// ── angular.json — build configurations ──────────────────────────────────
// "configurations": {
//   "production": {
//     "fileReplacements": [{
//       "replace": "src/environments/environment.ts",
//       "with":    "src/environments/environment.prod.ts"
//     }],
//     "optimization": true,
//     "sourceMap": false,
//     "aot": true
//   },
//   "staging": {
//     "fileReplacements": [{
//       "replace": "src/environments/environment.ts",
//       "with":    "src/environments/environment.staging.ts"
//     }]
//   }
// }

// ── Typed injection token — preferred over direct import ──────────────────
// src/app/core/tokens/app-config.token.ts
import { InjectionToken } from '@angular/core';

export interface AppConfig {
  apiUrl:          string;
  production:      boolean;
  enableDebugTools: boolean;
  appVersion:      string;
}

export const APP_CONFIG = new InjectionToken<AppConfig>('APP_CONFIG');

// ── app.config.ts — provide the token ────────────────────────────────────
import { environment } from '../environments/environment';
import { APP_CONFIG }  from './core/tokens/app-config.token';

export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(routes),
    provideHttpClient(withInterceptors([authInterceptor, errorInterceptor])),
    { provide: APP_CONFIG, useValue: environment },  // inject env as typed config
  ],
};

// ── Service using the token (testable — no direct env import) ─────────────
@Injectable({ providedIn: 'root' })
export class PostsApiService {
  private config = inject(APP_CONFIG);
  private http   = inject(HttpClient);

  private apiUrl = `${this.config.apiUrl}/api/posts`;

  getPublished(page = 1, size = 10): Observable<PagedResult<PostSummaryDto>> {
    return this.http.get<PagedResult<PostSummaryDto>>(this.apiUrl, {
      params: { page, size },
    });
  }
}
Note: The InjectionToken approach makes services unit-testable without file system dependencies. In tests, provide the token with test values: { provide: APP_CONFIG, useValue: { apiUrl: 'http://test-api', production: false } }. If services imported the environment file directly, tests would use the development environment values, which may differ from the test environment or require mocking file imports. The injection token approach follows Angular’s DI principles and produces cleaner, more portable services.
Tip: Add appVersion to the environment and display it in the footer or a debug overlay. When users report bugs, knowing the exact app version helps diagnose whether the issue was already fixed in a newer deployment. Also useful in Angular error interceptors — include the app version in error reports sent to a monitoring service (Sentry, Application Insights) for correlation with deployment timelines.
Warning: Environment files are compiled into the JavaScript bundle — anything in them is visible to anyone who downloads the app. Never put secrets (API keys, private tokens, database credentials) in environment files. API keys for client-side services (maps, analytics, payment UI SDKs) are acceptable — they are designed to be public. Authentication tokens, server-side API keys, and database connection strings must never appear in environment files.

Common Mistakes

Mistake 1 — Importing environment directly in services (not testable)

❌ Wrong — import { environment } from '../../environments/environment' in a service; hard to test without file mocking.

✅ Correct — inject APP_CONFIG token; provide test values in unit test providers.

Mistake 2 — API secrets in environment files (exposed in browser bundle)

❌ Wrong — server-side API key in environment.prod.ts; compiled into JS bundle; visible in browser DevTools.

✅ Correct — server-side secrets in server environment variables only; Angular environment files are public.

🧠 Test Yourself

ng build --configuration=production is run. Which environment file is used for the build?