Angular Blueprint: Environment Variables

Yevhen Chmykhun
8 min readMay 18, 2023

--

Environment variables are an important concept in software development that allows developers to easily manage configuration options for different environments such as development, testing, and production. Managing different environment variables becomes crucial when a software application needs to be deployed in different environments, each with its own specific set of options.

Environment variables also improve the security of an application by allowing developers to protect sensitive data such as API keys and database credentials for different environments. For example, in a production environment, it is important to protect sensitive data and limit access to those who are authorized to access them. By using environment variables, developers can easily store sensitive information in an encrypted format, only accessible to authorized members of the development team.

In this article, we’ll discuss environment files and configuration management systems, and implement a custom setup for managing environment variables designed to combine the upsides of the previous two approaches.

Environment Files

Angular provides a built-in mechanism for managing environment variables through the use of environment files. Environment files are JSON files that contain environment configuration variables, and they are located in the src/environments folder. By default, Angular provides two environment files:

  • environment.ts: The default development environment.
  • environment.prod.ts: The production environment.

These files contain variables that are specific to each environment. For example, the environment.ts file might contain variables for a development API endpoint, while the environment.prod.ts file might contain variables for a production API endpoint. When you build an application, the environment files are compiled into the application code. This means that the application can be configured differently for different environments without the need for separate code bases.

Although environment files can be useful for defining environment variables, there are some potential downsides to consider:

  • Security: Since the environment.ts file is included in the built application, any sensitive information that is stored in this file is accessible to anyone who can access the application’s code. This can include things like API keys, passwords, and other sensitive data. For this reason, it is generally not recommended to store sensitive information in the environment.ts file.
  • Build-time configuration: The environment.ts file is typically used for build-time configuration, which means that any changes to the environment settings require a rebuild of the application. This can slow down the development process, especially if the application needs to be rebuilt frequently.

To mitigate these downsides, it may be necessary to use other tools or techniques for managing environment variables, such as configuration management systems. Additionally, it is important to carefully consider the security implications of storing sensitive information in the environment.ts file and to take appropriate measures to protect this information.

Configuration Management Systems

Configuration management systems are tools that allow you to manage configuration settings for an application across different environments. These systems provide a centralized way to manage configuration settings, making it easier to deploy and maintain applications in different environments.

There are several configuration management systems that can be used with Angular, including:

  • AWS Systems Manager Parameter Store: This is a managed service from Amazon Web Services (AWS) that provides a secure and scalable way to store application configurations, including environment variables, in the cloud.
  • Azure App Configuration: This is a managed service from Microsoft Azure that provides a central location to manage application settings, feature flags, and other configurations.
  • Google Cloud Config Connector: This is a Kubernetes-native service from Google Cloud Platform (GCP) that enables developers to store and manage configuration data as Kubernetes resources. With Google Cloud Config Connector, developers can manage configurations for different environments, and use Kubernetes’ declarative syntax to automate configuration updates.

Configuration management systems offer benefits for managing environment settings, such as:

  • Centralized management: Configuration management systems provide a centralized way to manage configuration settings for an application across different environments. This makes it easier to deploy and maintain applications in different environments.
  • Secure storage: Configuration management systems often provide secure storage for sensitive configuration data, such as API keys and passwords.
  • Automation: Configuration management systems can be integrated with automation tools, such as CI/CD pipelines, to automate the deployment and configuration of applications.
  • Versioning: Configuration management systems often provide versioning and history tracking for configuration data, making it easier to track changes and revert to previous versions if necessary.

While configuration management systems offer many benefits for managing environment variables, there are also some potential downsides to consider:

  • Increased complexity: Configuration management systems can add additional complexity to your application deployment and configuration process. Depending on the system you choose, you may need to learn new tools or technologies to use it effectively.
  • Dependency on external services: Using a configuration management system often means relying on an external service to store and manage your configuration data. This can introduce additional dependencies into your application and make it more difficult to troubleshoot issues related to configuration settings.
  • Security concerns: Configuration management systems can introduce security concerns if they are not configured properly. For example, if your configuration data contains sensitive information, such as API keys or passwords, it is important to ensure that this data is stored securely and that only authorized users have access to it.
  • Increased deployment time: Depending on the size and complexity of your application, using a configuration management system may add additional time to your deployment process. This is because you will need to ensure that your configuration settings are properly configured and deployed along with your application code.
  • Additional cost: Some configuration management systems, such as AWS Parameter Store, may require additional costs to use. This can be a concern for smaller applications or development teams with limited budgets.

Custom Setup

Creating a custom setup can be an effective way to overcome the limitations of traditional configuration approaches, such as not being able to change the environment variables at runtime in case of using environment files or additional costs to the project in case of using configuration management systems. While it may require additional development effort at first, the benefits of a custom setup can ultimately save time and provide a more tailored and reliable solution for application deployment.

Let’s consider one possible way to implement a custom setup that is specifically designed to address the downsides of the previous approaches. This solution involves creating a configuration file that can be integrated into the application’s build output, allowing developers to modify the environment variables at runtime.

To create this setup, first, we will define the configuration file format and specify the parameters that can be modified. Then, we will integrate the configuration file into the application build process, ensuring that it is included in the final output. As a result, it will be possible to modify the environment variables as needed before running the application. Below are the steps to implement a setup like this.

Create app-config.ts file in src/app/core/model folder and define the AppConfig interface and the APP_CONFIG injection token:

import { InjectionToken } from "@angular/core";

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

export interface AppConfig {
name: string;
env: string;
api: string;
}

The InjectionToken class is used to create a unique token that can be used for dependency injection in Angular. In this case, the token is being created with a type parameter of AppConfig, which is an interface defined right after it.

The AppConfig interface defines an object with three properties: name, env, and api. The purpose of this interface is to define the structure of the application’s configuration settings. These settings may vary depending on the environment.

By defining the APP_CONFIG constant as an InjectionToken of type AppConfig, it can be used to inject the application’s configuration settings into other Angular components, services, or modules. For example, a component that needs to access the application’s API endpoint could define a constructor parameter with the type AppConfig and Angular’s dependency injection system would automatically inject the APP_CONFIG token with the corresponding configuration settings.

Create app.conf.json file in src folder with the following content:

{
"name": "FinServe",
"env": "$env$",
"api": "$api$"
}

In this configuration file, the application name is defined as FinServe, and it also specifies two placeholders that will be replaced with actual values at runtime. The placeholders are surrounded by dollar signs (could be any sign or pattern of your choice) and represent environment and API values.

The process of replacing these placeholders with actual values is typically done by a Continuous Integration/Continuous Delivery (CI/CD) tool, such as Jenkins, CircleCI, or Travis CI. These tools automate the process of building, testing, and deploying applications, and they allow for configuration files to be customized based on the environment where the application is being deployed. This ensures that the application is correctly configured for its intended use case.

Update the main.ts file that is located in src folder:

import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
import { APP_CONFIG } from './app/core/model/app-config';
import { Provider } from '@angular/core';

fetch('./config/app.conf.json')
.then(response => response.json())
.then(config => {
const appConfigProvider: Provider = {
provide: APP_CONFIG,
useValue: config
}
platformBrowserDynamic([appConfigProvider])
.bootstrapModule(AppModule)
.catch(err => console.error(err));
});

This code dynamically loads an Angular application with configuration values provided by a JSON file, which allows for the application to be easily configured for different environments.

To retrieve the configuration values, the code reads the app.conf.json file using the fetch() method, and then processes the response with a promise chain. The fetched JSON object is then used to create an appConfigProvider object, which is a provider for the APP_CONFIG injection token. The provider is used to inject the configuration values into the application at runtime.

The platformBrowserDynamic() method is then called with the appConfigProvider object passed as a parameter. This method bootstraps the application module with the configuration values injected via the provider.

Finally, we need to make sure that the app.conf.json file is copied to the correct location during application build. This is achieved by updating the angular.json file:

"build": {
...
"options": {
...
"assets": [
...
{
"input": "src",
"output": "config/",
"glob": "app.conf.json"
}
]
}
}

The build section of the angular.json file defines the configuration for building an Angular application. This specific part of the build section defines the options for building the application and specifies how the application’s assets should be handled during the build process.

Within the options object, there is an assets array that defines the list of assets that need to be copied from the source directory to the output directory during the build process.

The input property specifies the source directory for the asset files, which in this case is the src directory. The output property specifies the destination directory where the asset files will be copied during the build process. In this case, the asset files will be copied to the config/ directory.

Finally, the glob property specifies the pattern used to match the asset files that need to be copied. In this case, the pattern is app.conf.json, which means that only the app.conf.json file in the src directory will be copied to the config/ directory during the build process.

Now we can inject the application configuration options in any part of the application. Here is how to inject it as a class field:

private readonly appConfig = inject(APP_CONFIG);

Conclusion

Environment variables are a crucial part of software development, allowing developers to easily manage configuration options for different environments, maintain different configurations, and improve the security of an application. With environment variables, developers can work more efficiently and deploy applications with confidence, knowing that their configurations are optimized for each specific environment.

FinServe

FinServe is an Angular application that demonstrates various technical aspects of web application development. This application was specifically designed to showcase the implementation of key features and functionalities such as working with forms, integrating with APIs, handling authentication and authorization, and more.

If you’re interested in checking out the code behind FinServe, you can find the GitHub repository here. From there, you can explore the application’s architecture, view its dependencies, and even contribute to its ongoing development.

--

--