Click here to Skip to main content
15,868,004 members
Articles / Programming Languages / Typescript

Persistent User Settings with Angular 2+

Rate me:
Please Sign up or sign in to vote.
4.33/5 (5 votes)
6 Sep 2017MIT4 min read 16.1K   132   2  
Presentation of handling persistent user settings in Angular 2+ front end applications

Image 1

Introduction

Angular 2 is a relatively new framework with less online help and code snippets available for now. Code sections/modules shared can be therefore valuable to help others deal with this excellent framework. In this article I present a small component and a service for handling persistent user settings in an Angular 2 app. The settings-service contains the core functions to handle settings: storing, saving and retrieving the settings, and the settings-component holds and manages the view to display the settings, interact with the user and transfer the user input to settings-service. To use this tutorial you will need to know how to create an Angular 2+ app and how to create and wire up components and services.

Settings data

The starting point of our settings project is defining the data structure of user settings. User settings are generally answers to single choice questions (displayed via radio buttons). In our applications the user can have the site displayed in English or German {'language': ['English', 'German']}, or the temperature in degrees of Celsius or Fahrenheit. Note: multiple choice questions are also possible types of user settings (displayed via checkmarks). These can be handled as extensions of single-choice settings. For the sake of simplicity, we only deal with single-choice settings in this article.

Single choice settings can be represented the easiest in a Javascript object literal, where the key is the type of setting, and the value is an array of the possible values for the key. The resulting object literal contains all options available to the user. The component will use these settingsValues through an accessor functions (getSettingsValues()) to populate the view; and the service will use the settings object literal for checking permissible values. The service and the component rely solely on this object literal, so changing or adding settings will require no other changes to the code and will not break any existing code.

private static settingsValues: {} = {
  'LANGUAGE': ['EN', 'DE', 'HU', 'RO'],
  'FORECAST_PERIOD': ['FULL_DAY', 'DAYTIME'],
  'WIND_SPEED': ['KNOTS', 'KMH', 'MPH', 'MS'],
  'WIND_DIRECTION': ['ARROW', 'CARDINAL', 'DEGREE'],
  'TEMPERATURE': ['C', 'F'],
  'PRESSURE': ['HPA', 'MB', 'INCHES'],
  'PRECIPITATION': ['MM3H', 'IN3H'],
  'ALTITUDE': ['M', 'FT'],
  'LAPSE_RATE': ['C100M', 'F1000FT']
};

public getSettingsValues() {
  return SettingsService.SettingsValues;
}

Populating the view

In settings-component we store the keys and the key-value pairs in two objects to build the view. We also store the current settings selected by the user in a third object. The currentSettings are required to display the current values in the view (via ngModel).

private settingsKeys = Object.keys(this.settingsService.getSettingsValues());
private settingsValues = this.settingsService.getSettingsValues();
private currentSettings = {};

When initializing the component we fill currentSettings with the {key: value} pairs of current user settings retrieved from settings-service:

initSettings() {
  for (const key of this.settingsKeys) {
    this.currentSettings[key] = this.settingService.getSetting(key);
  }
}

ngOnInit() {
  this.initSettings();
}

The view is generated based on settingsKeys and settingsValues. We have an outer ngFor cycle to display the settingsKeys in a radio group. Note: The translate pipe is used to translate the keys into user-readable text in the selected language. ngx-translate is used for translations, but not detailed in this article. There is an inner ngFor cycle to display the available values as radio buttons for each settings key.

<form role="form">
    <div class="form-group">
	
      <div *ngFor="let key of settingsKeys">
        <label>{{key|translate}}</label><br>	  
        <label *ngFor="let value of settingsValues[key]"
                class="radio-inline">
          <input type="radio"
                (click)="saveSetting(key, value)"
                [(ngModel)]="currentSettings[key]"
                name="{{key}}"
                value="{{value}}">
                {{value|translate}}
        </label>
      </div>

    </div>
</form>

When the user clicks on a radio button value, it triggers the function saveSetting(key, value) with the key and the selected value as parameters. This function calls in turn the setSetting() function of settings-service:

saveSetting(key: string, value: string) {
  this.settingService.setSetting(key, value);
}

Wiring up the service

Now that we have our view set up, all we need to implement are the getter and setter functions in settings-service. There are three public functions: getSettingsValues(), getSetting() and setSetting(). For implementing these functions we have many different options available: we can store the settings in a local object (not persistent) or persist them to localStorage or to the server. In this article we chose to use localStorage to persist the settings between sessions. If localStorage is not available, we have a fallback to storing the settings in a JSON object to have persistence at least for the session. First we implement the code to store settings in an object:

private settingsObject = {}; // store settings of a session if localStorage is not available

private initSettingsObject() {
  // create settings object to store settings for session if localStorage is not available
  // settingsObject serves also as a fallback before the user sets any personal settings
  for (const key of Object.keys(SettingsService.forecastSettingsValues)) {
    this.settingsObject[key] = SettingsService.forecastSettingsValues[key][0];
  }
}

constructor() {
  this.initSettingsObject();
}

We finally arrived to the public functions of settings-service: getSetting() ang setSetting(). First we check if the setting is valid or not (key for getSetting() and {key: value} pair for setSetting()), then retrieve the setting from or save it to local storage or the settingsObject if local storage or the setting in local storage is not available.

public getSetting(key: string): string {
  if (!this.isValidKey(key)) {
    console.log('settings-service.getForecastSetting() - key is invalid: ' + key);
    return '';
  }

  if ((typeof(Storage) !== 'undefined') && localStorage.getItem(key)) {
    return localStorage.getItem(key);
  } else {
    return this.settingsObject[key];
  }
}

public setSetting(key: string, value: string) {

  if (!this.isValidSetting(key, value)) {
    console.log('settings-service: {' + key + ': ' + value + '} setting was incorrect');
    return;
  }

  if ((typeof(Storage) !== 'undefined')) {
    localStorage.setItem(key, value);
  } else {
    this.settingsObject[key] = value;
  }
}

There are two more helper functions in settings-service to check if a key or a {key: value} is valid: isValidKey() and isValidSetting():

public isValidKey(key: string) {
  if (SettingsService.forecastSettingsValues.hasOwnProperty(key)) {
        return true;
      } else {
        return false;
      }
}

public isValidSetting(key: string, value: string) {
  if (SettingsService.forecastSettingsValues.hasOwnProperty(key) &&
      SettingsService.forecastSettingsValues[key].includes(value)) {
        return true;
      } else {
        return false;
      }
}

Conclusions

The code is robust because changing the settings does not require any change to the code or the html. There is one design flaw in the code though: when querying a setting in other parts of the applications calling settingsService.getSetting(), the code has to know the string literal of the key (ex. 'TEMPERATURE'). So if a key is changed, other parts of the code using this key will break.

License

This article, along with any associated source code and files, is licensed under The MIT License


Written By
Software Developer Softingrom SRL
Romania Romania
Having studied computer science and arts, I have alwats tried to be present in both fields to earn a living and satisfy my professional curiosity. Programming and computer science have been my hobbies for 10+ years, and gradually grew a profession since 2016. I am presently working as a software developer in the field of vehicle diagnostics, measurement and testing in C++ and Python.

Languages and technologies I have work experience with:
Web development: PHP, Javascript, AngularJS, Angular2, HTML5, CSS3

C++11, Boost libraries, Python

SW development technologies and tools:
Agile SW dev, TFS, iceScrum, git, SVN, VS2012, VS2015, Jenkins

Automotive bus systems: K-Line, CAN, LIN, FlexRay
Vehicle communication protocols: KWP2000, ISOTP, UDS

Comments and Discussions

 
-- There are no messages in this forum --