Application configuration
Angular Best Practices Dependency Injection Patterns Typescript Visual Studio Code

Using Angular Dependency Injection in an Application Configuration

Welcome to today’s post.

In today’s post I will show how to use dependency injection within Angular to provide configuration within any component within your application.

Recall that dependency injection is an implementation pattern from the SOLID principle of dependency inversion that we use to decouple classes that are higher level from classes that are at a lower level. By decoupling classes and components within an application, we make the components within the application more testable. In addition, by injecting functionality into a class, we remove the dependency of the functionality from the class, with only the abstraction as a dependency.

I will be showing how we use injection tokens within any class within the application so that configuration files are decoupled from the classes.

Before I show how injection tokens are used, in the first section, I will show how we inject classes into other classes within Angular applications.

Dependency Injection of Components in Angular Classes

In Angular applications you may have seen how dependency injection is used to inject providers into our application components. For example, the most common usage is to inject custom application services into components. One most common example is an API service class which we declare as injectable:

@Injectable()
export class ApiService {

	…

    constructor(private http: HttpClient) {}

Then within our UI component TS file we declare the service within the constructor as a private variable which is then available for access within our class:

export class BookReturnComponent implements OnInit {
	…

  constructor(private api: ApiService, 
	…
  ) {}

  ngOnInit() {

To be able to achieve this we declare our service within the app module @NgModule providers array as shown:

  providers: [
    ApiService, 
	…

Now I will show how to setup a basic application configuration that can be injected within our components or classes using an injection token,

Using Injection Tokens in Angular Classes

We declare a configuration class to hold our application configuration properties. The declaration should be in a separate typescript file within the scope of our app folder.

export class AppConfig {
    baseApiUrl: string;
    baseLoanApiUrl: string;
    baseIdentityApiUrl: string;
    maxRowsPerPage: string;
    title: string;
}

We then declare an injection token, which we will be using to provide the reference to our application configuration properties.

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

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

Next, we declare the object that will hold our application configuration values:

export const BOOKS_DI_CONFIG: AppConfig = {
    baseApiUrl: "http://localhost/BookLoan.Catalog.API/api",
    baseLoanApiUrl: "http://localhost/BookLoan.Loan.API/api",
    baseIdentityApiUrl: "http://localhost/BookLoan.Identity.API/api",
    maxRowsPerPage: "20",
    title: "Angular Demo App"
};

The next step is to declare our injection token as a provider for our component. In this case we use the injection token BOOKS_DI_CONFIG as a value for our provider APP_CONFIG.

  providers: [
    ApiService, 
    {
      provide: APP_CONFIG, 
      useValue: BOOKS_DI_CONFIG
    },
    NotificationService, 

To make our configuration provider available within our component or service we inject the token through the constructor:

export class ApiService {
    private selectedBook = new Subject<any>();
    bookSelected = this.selectedBook.asObservable();

    constructor(
        @Inject(APP_CONFIG) private config: AppConfig, private http: HttpClient) {}

Note: If we had declared our AppConfig as an interface:

export class AppConfig {
    baseApiUrl: string;
    baseLoanApiUrl: string;
    baseIdentityApiUrl: string;
    maxRowsPerPage: string;
    title: string;
}

Then the injection token would not work the way we want and would give us an undefined for the appConfig variable within our constructor. Interfaces are supported within Typescript, but are not within JavaScript, which is an object-based language, so your application once built and run would not be able to create an instance object out of the interface.

After we have built and run our application the configuration object, once we have set a breakpoint within our target component should have all the configuration data populated within it:

To use the configuration within our component we just refer to the configuration object:

getBooks(): Observable<Book[]> {
    return this.http.get<Book[]>(this.config.baseApiUrl + '/Book/List'); 
}

If we wish to make our configuration flexible enough to provide us with configurations that are relevant to the current environment (development, test, production), then we can amend our BOOKS_DI_CONFIG object to reference the environment object:

export const BOOKS_DI_CONFIG: AppConfig = {
    baseApiUrl = environment.baseApiUrl,
    baseLoanApiUrl = environment.baseLoanApiUrl,
    baseIdentityApiUrl = environment.baseIdentityApiUrl,
    maxRowsPerPage: "20",
    title: "Angular Demo App"
};

As we can see, using dependency injection within our Angular application allows us to separate the concerns of configuration within our code base. We can still separate the configurations of the three main phases of the development environment lifecycles development, test, and production from the Angular environment class and bind the configuration parameters from each these environments within each of our build lifecycle.

That’s all for today’s post.

I hope you found it useful and informative.

Social media & sharing icons powered by UltimatelySocial