Skip to main content

Overview

The Angular PWA Demo uses a flexible configuration system that supports multiple environments, external JSON configuration files, and build-time settings.

Configuration architecture

The application uses a multi-layered configuration approach:
1

Build configuration

angular.json defines build settings, assets, and optimization levels
2

Environment files

TypeScript environment files for compile-time configuration
3

External JSON config

Runtime configuration loaded from assets/config/config.json
4

ConfigService

Centralized service for accessing configuration values

Angular.json configuration

The main build configuration is in angular.json:
{
  "projects": {
    "AngularDijkstra": {
      "projectType": "application",
      "sourceRoot": "src",
      "prefix": "app",
      "architect": {
        "build": {
          "builder": "@angular/build:application",
          "options": {
            "allowedCommonJsDependencies": [
              "three",
              "@tweenjs/tween.js"
            ],
            "outputPath": {
              "base": "dist/PWA_DEMO_ENV_PROD/"
            },
            "index": "src/index.html",
            "polyfills": [
              "zone.js",
              "@angular/localize/init"
            ],
            "tsConfig": "tsconfig.app.json",
            "assets": [
              "src/favicon.ico",
              "src/assets",
              "src/manifest.webmanifest"
            ],
            "styles": [
              "node_modules/bootstrap/dist/css/bootstrap.min.css",
              "@angular/material/prebuilt-themes/indigo-pink.css",
              "src/styles.css"
            ],
            "scripts": [],
            "serviceWorker": "ngsw-config.json",
            "browser": "src/main.ts"
          }
        }
      }
    }
  }
}

Key configuration options

Allows CommonJS modules that would otherwise trigger warnings. Used for Three.js and Tween.js compatibility.
Specifies where build artifacts are generated. Set to dist/PWA_DEMO_ENV_PROD/ for GitHub Pages deployment.
Files and folders copied as-is to the output directory. Includes favicon, static assets, and PWA manifest.
Global stylesheets loaded in order. Includes Bootstrap, Material Design theme, and custom styles.
Enables PWA service worker using the configuration in ngsw-config.json.

Build configurations

Define different configurations for development and production:
{
  "configurations": {
    "production": {
      "budgets": [
        {
          "type": "initial",
          "maximumWarning": "2mb",
          "maximumError": "25mb"
        },
        {
          "type": "anyComponentStyle",
          "maximumWarning": "5mb",
          "maximumError": "5mb"
        }
      ],
      "outputHashing": "all"
    }
  },
  "defaultConfiguration": "production"
}
Use ng build --configuration development for faster builds during development with debugging enabled.

Runtime configuration with ConfigService

The ConfigService loads external JSON configuration at runtime from src/app/_services/__Utils/ConfigService/config.service.ts:1:
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { _environment } from 'src/environments/environment';

@Injectable({
  providedIn: 'root'
})
export class ConfigService {
  constructor(
    protected http: HttpClient,
    public route: ActivatedRoute
  ) {}

  // Load main configuration file
  loadConfig() {
    return this.http.get('./assets/config/config.json').toPromise()
      .then((data: any) => {
        _environment.externalConfig = data;
      })
      .catch(error => {
        console.error('Error loading configuration:', error);
      });
  }

  // Get configuration value by key
  getConfigValue(key: string) {
    let jsonData: string = JSON.parse(
      JSON.stringify(_environment.externalConfig)
    )[key];
    return jsonData;
  }

  // Load page configuration
  _loadMainPages(): Promise<void> {
    return new Promise((resolve) => {
      this.http.get('./assets/config/mainPages.json').toPromise()
        .then((data: any) => {
          _environment.mainPageList = data;
          _environment.mainPageList.forEach((element: MainPage) => {
            _environment.mainPageListDictionary[element.log_name] = element;
          });
          resolve();
        })
        .catch(error => {
          console.error('Error loading configuration:', error);
        });
    });
  }
}

External configuration files

Create configuration files in src/assets/config/:

config.json

Main application configuration:
{
  "appBrand": "Angular PWA Demo",
  "appVersion": "1.0.0",
  "baseUrlNetCore": "https://api.example.com/",
  "baseUrlNetCoreCPPEntry": "https://cpp-api.example.com/",
  "baseUrlNodeJs": "https://nodejs-api.example.com/",
  "baseUrlSpringBootJava": "https://java-api.example.com/",
  "baseUrlDjangoPython": "https://python-api.example.com/"
}

mainPages.json

Page configuration for dynamic routing:
[
  {
    "log_name": "landing",
    "page_name": "Home",
    "route": "/",
    "pages_nested": [
      {
        "name": "Dashboard",
        "route": "/dashboard",
        "queryParams": "{view: grid}"
      }
    ]
  }
]
External configuration files are loaded at runtime. Don’t include sensitive data like API keys - use environment variables instead.

Using configuration in components

Inject and use the ConfigService with Angular 21’s inject() function:
import { Component, OnInit, inject, signal } from '@angular/core';
import { ConfigService } from './_services/__Utils/ConfigService/config.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  standalone: false
})
export class AppComponent implements OnInit {
  // Inject ConfigService
  private readonly _configService = inject(ConfigService);

  // Reactive state with signals
  public readonly appBrand = signal<string>('');
  public readonly appVersion = signal<string>('');

  ngOnInit(): void {
    this.initializeConfig();
  }

  private initializeConfig(): void {
    // Get values from configuration
    const brand = this._configService.getConfigValue('appBrand') ?? 'App';
    const version = this._configService.getConfigValue('appVersion') ?? '1.0.0';

    // Update signals
    this.appBrand.set(brand);
    this.appVersion.set(version);
  }
}

Application initialization

Load configuration before the app starts using APP_INITIALIZER:
import { APP_INITIALIZER, ApplicationConfig } from '@angular/core';
import { ConfigService } from './services/config.service';

export function initializeApp(configService: ConfigService) {
  return (): Promise<void> => {
    return configService.loadConfig();
  };
}

export const appConfig: ApplicationConfig = {
  providers: [
    {
      provide: APP_INITIALIZER,
      useFactory: initializeApp,
      deps: [ConfigService],
      multi: true
    }
  ]
};
Using APP_INITIALIZER ensures configuration is loaded before any components are initialized.

Environment-specific configuration

Create different config files for each environment:
src/assets/config/
├── config.json          # Default configuration
├── config.dev.json      # Development overrides
├── config.staging.json  # Staging environment
└── config.prod.json     # Production settings
Load the appropriate file based on environment:
loadConfig(environment: string = 'prod') {
  const configFile = `./assets/config/config.${environment}.json`;
  return this.http.get(configFile).toPromise()
    .then((data: any) => {
      _environment.externalConfig = data;
    })
    .catch(error => {
      console.error('Error loading configuration:', error);
      // Fallback to default config
      return this.http.get('./assets/config/config.json').toPromise();
    });
}

TypeScript configuration

The project uses strict TypeScript settings in tsconfig.json:
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ES2022",
    "lib": ["ES2022", "dom"],
    "strict": true,
    "strictNullChecks": true,
    "noImplicitAny": true,
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  }
}

PWA configuration

Configure the Progressive Web App features in src/manifest.webmanifest:
{
  "name": "Angular PWA Demo",
  "short_name": "PWA Demo",
  "theme_color": "#1976d2",
  "background_color": "#fafafa",
  "display": "standalone",
  "scope": "./",
  "start_url": "./",
  "icons": [
    {
      "src": "assets/icons/icon-72x72.png",
      "sizes": "72x72",
      "type": "image/png",
      "purpose": "maskable any"
    },
    {
      "src": "assets/icons/icon-192x192.png",
      "sizes": "192x192",
      "type": "image/png",
      "purpose": "maskable any"
    },
    {
      "src": "assets/icons/icon-512x512.png",
      "sizes": "512x512",
      "type": "image/png",
      "purpose": "maskable any"
    }
  ]
}

Service worker caching

Configure caching strategies in ngsw-config.json:
{
  "$schema": "./node_modules/@angular/service-worker/config/schema.json",
  "index": "/index.html",
  "assetGroups": [
    {
      "name": "app",
      "installMode": "prefetch",
      "resources": {
        "files": [
          "/favicon.ico",
          "/index.html",
          "/*.css",
          "/*.js"
        ]
      }
    },
    {
      "name": "assets",
      "installMode": "lazy",
      "updateMode": "prefetch",
      "resources": {
        "files": [
          "/assets/**",
          "/*.(svg|cur|jpg|jpeg|png|apng|webp|avif|gif|otf|ttf|woff|woff2)"
        ]
      }
    }
  ]
}

Caching strategies

Prefetch

Download resources during installation for instant availability

Lazy

Cache resources only when first requested

Query parameters

The ConfigService includes a utility for reading URL query parameters:
queryUrlParams(paraName: string): string {
  let returnValue = "";
  this.route.queryParams.subscribe(params => {
    returnValue = params[paraName] ? params[paraName] : "";
  });
  return returnValue;
}
Usage in components:
const userId = this._configService.queryUrlParams('userId');
const mode = this._configService.queryUrlParams('mode');

Utility functions

The ConfigService provides helper utilities:

Generate GUID

generateGuid(): string {
  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (char) => {
    const random = (Math.random() * 16) | 0;
    const value = char === 'x' ? random : (random & 0x3) | 0x8;
    return value.toString(16);
  });
}
Usage:
const uniqueId = this._configService.generateGuid();
// Output: "a1b2c3d4-e5f6-4789-a012-b3c4d5e6f7g8"

Best practices

Load essential configuration before the app starts to prevent race conditions.
Always use the nullish coalescing operator (??) when reading config values.
Never commit API keys, secrets, or credentials. Use environment variables or secret management.
Include an appVersion field to track configuration changes.
Add runtime validation to ensure required config values are present.
Define interfaces for configuration objects to get type checking and IntelliSense.

Next steps

Deployment guide

Learn how to deploy your application

Services guide

Create and use services

Build docs developers (and LLMs) love