Just like in .NET applications, you might want to have global configuration settings in your Angular applications that you can access from any component or service class. There are many approaches you can take for retrieving these global settings; I'm going to use a service that can be injected into any class. I think the flexibility of using a service is an ideal method for providing application-wide settings to any class that needs them.

This article illustrates how to create an Angular service to read and modify configuration settings. I'm going to use Visual Studio Code and Visual Studio 2017, along with C# and Angular 4. You're going to learn to create a class to hold configuration settings. These settings can be retrieved from a JSON file or from a Web API call. Once you've retrieved the settings, you can store them into local storage. Placing them into local storage allows your user to modify those settings.

Create a New Angular Project

If you're new to Angular, you are going to need the following tools installed on your computer.

  • Node.js
  • TypeScript
  • Angular
  • Angular CLI
  • An editor, such as Visual Studio or Visual Studio Code

In this article, I'm going to use Visual Studio Code and the Angular CLI to create the sample project. I'll assume that you have the tools listed above installed on your computer. If you haven't already installed them, please do so now, so that you can follow along with this article.

Open a command prompt on your computer and navigate to a folder where you typically create your development projects. For example, on my computer, I usually go to my D drive and go to a folder named \Samples. So, from the command prompt I enter the following commands.

D:
cd \Samples

Using the Angular CLI, I create a new project under the Samples folder using the following command.

ng new ConfigSample

Executing the ng new command creates a new folder named \LogSample. The new command creates the files needed to develop an Angular application. While you are still in the command prompt, navigate to the new folder using the following:

cd ConfigSample

Start Visual Studio Code by typing code followed by a space and a period. This tells Code to start and open the current folder.

code .

When you are in Visual Studio Code, click on the View menu and choose the Integrated Terminal... menu option. A small window opens at the bottom of Visual Studio Code. Into this command window, type the following command to start up the lite-server.

npm start

The lite-server is a small server that runs on your local computer on port 4200 by default. Start your browser of choice and navigate to the http://localhost:4200 and you should see a Web page that looks like Figure 1.

Figure 1: The start page created using Angular CLI
Figure 1: The start page created using Angular CLI

Create the Application Settings Service Classes

You're going to create a class to be used across many other component and service classes in your Angular application. Place this new class into a \shared folder located under \src\app. Create the \shared folder now.

Right mouse-click on the \shared folder and add a new file named appsettings.ts. Create a class in this new file with properties to hold the values you wish to use in your Angular application. For example, if you have a Product form that's used to add new products to a database, you might want to provide some default values for a couple of the fields. The following code includes a class named AppSettings with two properties; defaultUrl and defaultPrice.

export class AppSettings {
  defaultUrl: string = "http://www.pdsa.com"
  defaultPrice: number = 1
}

An AppSettingsService Class

It's now time to create an Angular service class to return an instance of the AppSettings class. Add a new TypeScript file under the \shared folder named appsettings.service.ts. Add the code shown in the following code snippet:

import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/of';
import { AppSettings } from "./appsettings";

@Injectable()
export class AppSettingsService {
  getSettings(): Observable<AppSettings> {
    let settings = new AppSettings();
    return Observable.of<AppSettings>(settings);
  }
}

The preceding code is a standard representation of an Angular service class. In the getSettings() method, create a new instance of the AppSettings class and return that object from this service. The main reason you create a service here is to provide the flexibility to change the implementation of how you retrieve the settings later. For example, you might choose to read the settings from a JSON file, or make a Web API call to get the settings. Any method that calls this service always makes the same call regardless of where those settings are stored. The calling methods don't know if the implementation changes; they still receive the same settings class.

The Consumer of the Settings Service

Now that you have a simple application settings service class created that can return an instance of the AppSettings class, test it by creating a consumer. You can build a unit test to call the service, but let's use something a little more real-world. In this section of the article, build a Product class, a Product HTML page, and a Product component class to add a new product. You're going to set default values from the AppSettings into the appropriate properties of a Product object, then display those properties on the HTML page.

Add a new folder named \product under the \src\app folder. Add a new file in this folder named product.ts. Add a few properties to this class to represent the typical properties associated with a product, as shown in the following code snippet.

export class Product {
  productId: number;
  productName: string;
  introductionDate: Date;
  price: number;
  url: string;
}

Create an HTML page to which you can bind the properties of the Product class. Add another new file within the \product folder and name it product-detail.component.html. Add the code shown in Listing 1.

Listing 1: Add an HTML page to display the Product properties.

<div *ngIf="product">
  <table>
    <tbody>
      <tr>
        <td>Product Name:</td>
        <td>
          <input [(ngModel)]="product.productName" />
        </td>
      </tr>
      <tr>
        <td>Introduction Date:</td>
        <td>
          <input [(ngModel)]="product.introductionDate" />
        </td>
      </tr>
      <tr>
        <td>Price:</td>
        <td>
          <input [(ngModel)]="product.price" />
        </td>
      </tr>
      <tr>
        <td>URL:</td>
        <td>
          <input [(ngModel)]="product.url" />
        </td>
      </tr>
    </tbody>
  </table>
  <div>
    <button (click)="saveProduct()">Save</button>
  </div>
</div>

Create a component class to work with the HTML page you just created. Add a new file within the \product folder named product-detail.component.ts. Add the appropriate import statements to create the component class. Import the Component class and the OnInit interface. You also need the Product class you just created. Finally, import the AppSettings and AppSettingsService classes in this new component class.

import { Component, OnInit } from "@angular/core";
import { Product } from './product';
import { AppSettings } from '../shared/appsettings';
import { AppSettingsService } from '../shared/appsettings.service';

Each component class you create must be decorated with the @Component() decorator function. Pass in an object to this function to supply some meta-data about how this component is used. The object passed in creates a selector property set to the value product-detail. This value is used as a new element on an HTML page to invoke this component class. You also specify the location of the HTML page you just created by using the templateUrl property.

@Component({
  selector: "product-detail", templateUrl: "./product-detail.component.html"
})

After the @Component decorator, export a class named ProductDetailComponent and make sure that it implements the OnInit interface. The ngOnInit() function is called after the ProductDetailComponent class is instantiated. Just write an empty ngOnInit() method for now, and a saveProduct() method too. The saveProduct() method is called from the button in the HTML you created previously.

export class ProductDetailComponent implements OnInit {
  ngOnInit(): void {
  }
  
  saveProduct(): void {
  }
}

Next, add a constructor to the ProductDetailComponent class. Into this constructor, add a private variable named appSettingsService that's of the type AppSettingsService. Adding this code in the constructor tells Angular to inject an instance of the service into this class when it creates an instance of this component.

constructor(private appSettingsService: AppSettingsService) {
}

In the HTML created earlier, you referenced a product object. Create that property named product now. Also, create a property to assign to the global settings retrieved from the AppSettingsService.

product: Product;
settings: AppSettings;

Write the code within the ngOnInit() method to call the getSettings() method on the appSettingsService object. The subscribe method calls getSettings() and receives the settings back from the service. Subscribe has three parameters: a success function, an error function, and a completed function. In the success function, set the result returned to the settings property in this class. Because all you're doing is returning a new instance of a class, you can safely ignore the error function. In the completed function, create a new instance of the Product object and assign the price and URL properties to the defaults returned from the settings object.

ngOnInit(): void {
  this.appSettingsService.getSettings().subscribe(settings => this.settings = settings, () => null, () => {
    this.product = new Product();
    this.product.price = this.settings.defaultPrice;
    this.product.url = this.settings.defaultUrl;
  });
}

Update AppModule

For your components and services to work within your application, you need to inform Angular of the service and the component. Open the app.module.ts file and add the following import statements.

import { FormsModule } from '@angular/forms';
import { ProductDetailComponent } from "./product/product-detail.component";
import { AppSettingsService } from "./shared/appsettings.service";

After you have the appropriate classes imported, modify the metadata properties of the @NgModule decorator function. Add the FormsModule class to the imports property. Add the ProductDetailComponent to the declarations property. Modify the providers property and add the AppSettingsService to the array. The @NgModule decorator function should now look like the following code snippet.

@NgModule({
  declarations: [AppComponent, ProductDetailComponent],
  imports: [BrowserModule, FormsModule], 
  providers: [AppSettingsService],
  bootstrap: [AppComponent]
})

Update AppComponent

Open the app.component.html file and delete all the code in this HTML file. Replace the code with the following.

<h1>Configuration Settings Sample</h1>
<div>
  <product-detail></product-detail>
</div>

Save all the changes to all your open files. Switch your open browser on the URL http://localhost:4200 and you should see your product page appear. Because the product property is bound to the different fields on the HTML page you created, the values are displayed automatically on the page, as shown in Figure 2.

Figure 2: The Price and URL fields are filled in with defaults from the AppSettings class.
Figure 2: The Price and URL fields are filled in with defaults from the AppSettings class.

Get Settings from a JSON File

In the previous sample, you hard-coded values into the AppSettings class. Instead of hard-coding the settings values, let's place those settings into a JSON file. If you don't already have one, create a folder called \assets under the \src folder. Add a JSON file named appsettings.json in the \assets folder. Add the following code into this file:

{
  "defaultUrl": "http://angular.io",
  "defaultPrice": 2
}

Modify AppSettingsService Class

Open the appsettings.service.ts file and add a few more import statements.

import { Http } from '@angular/http';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';

Import the Http class from @angular/http. Import the ReactiveJS operators map and catch. Modify the getSettings() method to call the http.get() method, passing in the path to the JSON file you created. Extract the response in the map() function and either return the JSON object from the response or an empty object. In the catch method, call a method named handleErrors(). The complete listing of the code that should be contained in the appsettings.service.ts file is shown in Listing 2.

Listing 2: Retrieve application settings from a JSON file.

import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
import 'rxjs/add/observable/of;
import 'rxjs/add/observable/catch;

import { AppSettings } from "./appsettings";

const SETTINGS_LOCATION = "assets/appsettings.json";

@Injectable()
export class AppSettingsService {
    constructor(private http: Http) {
    }
    
    getSettings(): Observable<AppSettings> {
        return this.http.get(SETTINGS_LOCATION)
            .map(response => response.json() || {})
            .catch(this.handleErrors);
    }
    
    private handleErrors(error: any): Observable<AppSettings> {
        // Return default configuration values
        return Observable.of<AppSettings>(new AppSettings());
    }
    
    private handleErrors(error: any): Observable<AppSettings> {
    }
}

Handling Exceptions

If the JSON settings file cannot be found, or if some other exception happens, the handleErrors() method is called. In addition to returning an AppSettings object, you might want to record the error information somewhere. In my last article, I created a logging system for Angular; use that if you want. For this article, just log the error to the console window. Regardless, the handleErrors() method returns an instance of an AppSettings class. You don't want your application to fail just because you can't get some specific global settings. So, return an instance of AppSettings with the appropriate defaults set. Make your handleErrors() method look like the following code snippet.

private handleErrors(error: any): Observable<AppSettings> {
  // Log the error to the console
  switch (error.status) {
    case 404:
      console.error("Can't find file: " + SETTINGS_LOCATION);
      break;
    default:
      console.error(error);
      break;
  }
  
  // Return default configuration values
  return Observable.of<AppSettings>(new AppSettings());
}

Update AppModule

If you don't already have it in your application, import the HttpModule from @angular/http in your app.module.ts file. Ensure that the HttpModule is listed in the imports property in the @NgModule() decorator function as well.

Save all the changes to the files you made. Go to the browser and you should now see the values from the JSON file appear in the price and the URL fields.

Using Local Storage

Retrieving the settings from the AppSettings class is great, but the user can't change those settings. To do that, you must store those settings somewhere. All modern browsers allow you to store key/pair values into a local storage area that persists between browser sessions. This storage is ideal for the global setting values presented in this article.

Saving Data into Local Storage

Open the appsettings.service.ts file and add a constant at the top of this file called SETTINGS_KEY. This constant contains the key value used for retrieving, storing, or deleting values in local storage.

const SETTINGS_KEY = "configuration";

Create a saveSettings() method that accepts an instance of an AppSettings class. Call the setItem() method on the localStorage object, passing in the key value contained in the SETTINGS_KEY constant as the first parameter and the AppSettings object as the second parameter. You need to stringify the AppSettings object in order to convert the JSON object to its appropriate string representation.

saveSettings(settings: AppSettings) {
  localStorage.setItem(SETTINGS_KEY, JSON.stringify(settings));
}

To test the new saveSettings() method, add a new button to the product-detail.component.html page. Call a method named saveSettings() from the click event of this button by writing the following code:

<button (click)="saveSettings()">
Save Settings
</button>

Open the product-detail.component.ts file and add the saveSettings() method to be called from the click event on the button.

saveSettings(): void {
  this.settings.defaultPrice = this.product.price;
  this.settings.defaultUrl = this.product.url;
  this.appSettingsService.saveSettings(this.settings);
}

In the saveDefaults() method, take the bound product properties and move them into the appropriate properties in the settings property of the ProductDetailComponent class. Call the saveSettings() method of the appSettingsService class that was injected by Angular into your ProductDetailComponent class.

Retrieve Settings and Store into Local Storage

Now that you have the ability to store values into local storage, you need to modify the getSettings() method in the appsettings.service.ts file to attempt to retrieve those values from local storage. If the values are found in local storage, return those values; otherwise return the values from the JSON file. Modify the getSettings() method to look like Listing 3.

Listing 3: Modify the getSettings() method to retrieve values from local storage

getSettings(): Observable<AppSettings> {
    let settings = localStorage.getItem(SETTINGS_KEY);
    
    if (settings) {
        return Observable.of(JSON.parse(settings));
    }
    else {
        return this.http.get(SETTINGS_LOCATION).map(response => {
            let settings = response.json() || {};
            if (settings) {
                this.saveSettings(settings);
            }
            
            return settings;
        })
        .catch(this.handleErrors);
    }
}

The getSettings() method attempts to get the settings object from local storage by passing the SETTINGS_KEY value to the getItem() method. If the variable named settings contains a value, create an AppSettings object using JSON.parse() and returning an Observable of the AppSettings object.

If nothing is found in local storage, retrieve the values from the file using the http.get() method. If the values are found in the file, save them into local storage by calling the saveSettings() method. After the first time calling the getSettings() method, succeeding calls to this method retrieve values from local storage. You only get the default values from the JSON file the first time.

Delete Settings

In many applications, the user can reset back to factory defaults. To accomplish the same thing in your Angular application, delete the values stored in local storage. If you delete all of the values, then the next time the getSettings() method is called, the original values from the JSON file are read. Add a deleteSettings() method to the AppSettingsService class.

deleteSettings(): void {
  localStorage.removeItem(SETTINGS_KEY);
}

Because the complete AppSettings object is stored within the one key in local storage, call the removeItem() method and pass in the SETTINGS_KEY constant to erase all the settings. To test this method, add a new button to the product-detail.component.html page in the sample.

<button (click)="deleteSettings()">
Delete Settings
</button>

Open the product-detail.component.ts file and add the deleteSettings() method. In this method, call the deleteSettings() method on the appSettingsService object that was injected into this component.

deleteSettings(): void {
  this.appSettingsService.deleteSettings();
}

Save all of your changes, switch back to your browser, and you should see your two new buttons appear on the screen. Try modifying the price and URL fields and click the Save Settings button. Refresh the page to ensure that the new values are retrieved from local storage. Next, try clicking on the Delete Settings button to delete the values from local storage. Refresh the page again to ensure that the values are retrieved from the JSON file.

Create Web API for Configuration Settings

In the last two examples in this article, you hard-coded global settings and retrieved them from a JSON file. Now you're going to retrieve settings by calling a Web API method and get the settings from a SQL Server table. To use a Web API call, you need to perform a few steps.

  • Create a new Web API project in Visual Studio
  • Create a SQL Server table named AppSettings
  • Create Entity Framework classes to retrieve data from the SQL table
  • Add a C# ConfigController class
  • Enable Cross-Origin Resource Sharing (CORS)
  • Convert C# PascalCase property names to JSON camelCase property names

Create a Visual Studio Web API Project

Open Visual Studio 2017 and select File > New > Project… from the main menu. From the New Project dialog, select Visual C# > Web > ASP.NET Web Application (.NET Framework). Set the name to ConfigWebApi and click the OK button. In the New ASP.NET Web Application dialog, select the Web API template and click the OK button. After a minute or two, you'll have a new Web API project created.

Create SQL Server Table

Create a table named AppSettings on an available SQL Server. This table should have column names that match the Angular AppSettings class. As with any proper database design, add a primary key for the table named AppSettingsId. Set this primary key as non-clustered, and add the IDENTITY attribute to start with the value of 1 and increment by 1 for each new record added. Below is the T-SQL statement to create this table in SQL Server.

CREATE TABLE dbo.AppSettings(
  AppSettingsId int NOT NULL CONSTRAINT PK_AppSettings PRIMARY KEY NONCLUSTERED IDENTITY(1,1),
  DefaultUrl nvarchar(255) NULL,
  DefaultPrice money NULL CONSTRAINT DF_AppSettings_DefaultPrice DEFAULT ((1))
)

After creating your table, add a new record to the table with some default values such as the following:

INSERT INTO AppSettings(DefaultUrl, DefaultPrice)
VALUES ('http://www.google.com', 42);

Create Entity Framework Classes

Right mouse-click on the Models folder in your Visual Studio project and choose Add > ADO.NET Entity Data Model… from the context-sensitive menu. Set the name of the data model to ConfigSample. Select Code First from Database from the first page of the Entity Data Model Wizard dialog and click the Next button. Create a new connection to the SQL Server and select the database where you created the AppSettings table and click the Next button. Drill down to the AppSettings table you created and click the Finish button. After about a second, two Entity Framework classes have been generated and added to your Models folder.

ConfigController Class

You're now ready to write your Web API controller and a method to retrieve the AppSettings values in your SQL Server table. Right mouse-click on the Controllers folder and select Add > Web API Controller Class (v2.1) from the menu. Set the name of this new controller to ConfigController and click the OK button. Add a using statement at the top of this file.

using ConfigWebApi.Models;

Delete all the methods within this class, as you don't need them. Add a Get() method, shown in Listing 4, to retrieve the settings from the SQL Server table. When writing a Web API method, return an object that implements the IHttpActionResult interface. The ApiController class has a few methods you call to create a return object. The Get() method creates an instance of the ConfigSample DbContext. Use the FirstOrDefault() method on the AppSettings collection to retrieve the first record in the AppSettings table. If a null is returned from this method, set the return variable to NotFound(). If a record is found, call the Ok() method and pass the settings variable into this method so that the values are returned from this method. If an error occurs, return an InternalServerError object.

Listing 4: Add a Get() method to your Web API controller to retrieve settings from a SQL table.

[HttpGet]
public IHttpActionResult Get()
{
    IHttpActionResult ret;
    ConfigSample db = null;
    AppSetting settings = new AppSetting();
    
    // Retrieve settings from SQL table
    try 
    {
        db = new ConfigSample();
        
        settings = db.AppSettings.FirstOrDefault();
        if (settings == null) 
        {
            ret = NotFound();
        }
        else 
        {
            ret = Ok(settings);
        }
    }
    catch (Exception) 
    {
        ret = InternalServerError(new ApplicationException("Error retrieving settings from AppSettings table."));
    }
    
    return ret;
}

Enable CORS

Because you created a new Visual Studio application to run your Web API method, it's running in a different domain from your Angular application. For your Angular application to call this Web API you must tell the Web API that you're allowing Cross-Origin Resource Sharing (CORS). Right-mouse click on your Web API project and select Manage NuGet Packages…. Click on the Browse tab and search for cors as shown in Figure 3. Install this package into your project.

Figure 3: Search for Microsoft CORS
Figure 3: Search for Microsoft CORS

After CORS is installed into your project, open the \App_Start\WebApiConfig.cs file and add the following line of code in the Register() method:

public static void Register(HttpConfiguration config)
{
    config.EnableCors();
    ...
}

Go back to your ConfigController class and add the following using statement:

using System.Web.Http.Cors;

Add the EnableCors() attribute just above your ConfigController class. You can get specific on the origins, headers, and methods properties to restrict access to only your Angular application if you want. For the purposes of this article, just set them to accept all requests by specifying any asterisk for each, as shown in the following code snippet:

[EnableCors(origins: "*", headers: "*", methods: "*")]
public class ConfigController : ApiController
{
    ...
}

Convert C# Class to JSON

C# property names are generally created using Pascal casing. Pascal casing means each letter of each word in the property name is initial-capitalized. The convention for naming properties in JavaScript and TypeScript is to use camel casing. Camel casing is where each word in the property name is initial capitalized except for the first word. So, the DefaultUrl property name in C# is expressed as defaultUrl in TypeScript. When the Web API serializes the AppSettings class to return it to your Angular application, it takes the property names of C# and translates them directly into JSON. You can change the serializer so that the Web API converts the Pascal-cased C# property names into camel-cased TypeScript property names.

Open the WebApiConfig.cs file and add a couple of using statements at the top of this file.

using System.Net.Http.Formatting;
using Newtonsoft.Json.Serialization;

Locate the Register() method and just below the first comment, add the lines shown in the following code snippet. Leave the rest of the code the same in the Register() method.

public static void Register(HttpConfiguration config)
{
    // Convert to camelCase
    var jsonFormatter = config.Formatters.OfType<JsonMediaTypeFormatter>().FirstOrDefault();
    jsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
    
    // Rest of the code below here
}

Run your Web API project and when it's running in the browser, copy the URL from the address line to the clipboard. You're going to need this address for your Angular service.

Modify AppSettingsService

Switch back to your Angular project in Visual Studio Code. Open the appsettings.service.ts file and locate the constant SETTINGS_LOCATION. Replace the contents of the value with what's in your clipboard. Then, add on “api/config” to the end. Your constant should now look like the following (with a different port number, of course).

const SETTINGS_LOCATION = "http://localhost:8314/api/config";

Switch to the browser that's running your Angular project. Click on the Delete Defaults button to delete the previous settings. Refresh your browser, and you should now see the values returned from your Web API call in the appropriate fields on the Web page.

Summary

In this article, you learned an approach for handling application-wide settings for Angular applications. A service approach is the most flexible approach for providing settings to any other class in your application. You can choose to store your settings in a class or in an external JSON file, or make a call to a Web API to retrieve the values. Store the settings retrieved into local storage to allow your users to modify those settings, if desired. If you delete the values from local storage, you allow your user to revert to the original default settings.