How to Deal with Different Form Controls in Angular 2

How to Deal with Different Form Controls in Angular 2 (with latest forms module)

Free Course

Getting Started with Angular 2

Angular 2 is the shiny new framework that comes with a lot of new concepts. Learn all the great new features.

In this tutorial we will explore the way to bind these few types of controls to our form:

  1. text
  2. number
  3. radio
  4. select (primitive type)
  5. select (object)
  6. multiple select
  7. checkbox (boolean)
  8. checkbox (toggle value)

Feel free to skip some of the control types (as some of them are really simple).

If you are new to Angular 2 forms, do refer to these articles for basics.

Introduction

We will build a form to capture user information based on these interfaces.

// user.interface.ts

export interface User {
    name: string; // text
    age?: number; // number
    gender?: string; // radio
    role?: string; // select (primitive)
    theme?: Theme; // select (object)
    topics?: string[]; // multiple select 
    isActive?: boolean; // checkbox
    toggle?: string; // checkbox toggle either 'toggled' or 'untoggled'
}

// theme.interface.ts

export interface Theme {
    display: string;
    backgroundColor: string;
    fontColor: string;
}

Here is how the UI will look:

Angular 2 Form Controls

App Setup

Here's our file structure:

|- app/
    |- app.component.html
    |- app.component.ts
    |- app.module.ts
    |- main.ts
    |- theme.interface.ts
    |- user.interface.ts
|- index.html
|- styles.css
|- tsconfig.json

In order to use forms module, we need to npm install @angular/forms npm package and import the forms module in application module.

$ npm install @angular/forms --save

Here's the module for our application app.module.ts:

// app.module.ts

import { NgModule }      from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';

import { AppComponent }   from './app.component';

@NgModule({
  imports:      [ BrowserModule, FormsModule ], // import forms module
  declarations: [ AppComponent ],
  bootstrap:    [ AppComponent ]
})

export class AppModule { }

The App Component

Let's move on to create our app component.

// app.component.ts

import { Component, OnInit } from '@angular/core';

import { User } from './user.interface';
import { Theme } from './theme.interface';

@Component({
    moduleId: module.id,
    selector: 'my-app',
    templateUrl: 'app.component.html',
    directives: []
})
export class AppComponent implements OnInit {
    public user: User;
    /* standing data goes here*/
    ...
    /* end standing data */
    ngOnInit() {
        // initialize user model here
    }
    public save(isValid: boolean, f: User) {
        console.log(f);
    }
}

Standing Data

We need to include some data for setup as well:-

// app.component.ts
...

/* standing data goes here*/
public genders = [
    { value: 'F', display: 'Female' },
    { value: 'M', display: 'Male' }
];
public roles = [
    { value: 'admin', display: 'Administrator' },
    { value: 'guest', display: 'Guest' },
    { value: 'custom', display: 'Custom' }
];
public themes: Theme[] = [
    { backgroundColor: 'black', fontColor: 'white', display: 'Dark' },
    { backgroundColor: 'white', fontColor: 'black', display: 'Light' },
    { backgroundColor: 'grey', fontColor: 'white', display: 'Sleek' }
];
public topics = [
    { value: 'game', display: 'Gaming' },
    { value: 'tech', display: 'Technology' },
    { value: 'life', display: 'Lifestyle' },
];
public toggles = [
    { value: 'toggled', display: 'Toggled' },
    { value: 'untoggled', display: 'UnToggled' },
];
/* end standing data */

...

Initialize model

Then, we need to initialize our user model:-

// app.component.ts
...

ngOnInit() {
    // initialize user model here
    this.user = {
        name: '',
        gender: this.genders[0].value, // default to Female
        role: null,
        theme: this.themes[0], // default to dark theme
        isActive: false,
        toggle: this.toggles[1].value, // default to untoggled
        topics: [this.topics[1].value] // default to Technology
    }
}
...

The HTML View

This is how our HTML view will look like.

<!-- app.component.html -->

<form #f="ngForm" novalidate>

    <!-- We'll add our form controls here -->

    <button type="submit" (click)="save(f.value, f.valid)">Submit</button>
</form>

Implementation

Let's start to look into each type of controls.

1. Text

Getting text input is very straight forward. You need the name attribute, and ngModel.

<!-- app.component.html -->
...
<div>
    <label>Name</label>
    <input type="text" name="name" [(ngModel)]="user.name">
</div>

...

2. Number

Getting number input is also very straight forward.

<!-- app.component.html -->
...
<div>
    <label>Age</label>
    <input type="number" name="age" [(ngModel)]="user.age">
</div>

...

3. Radio

Binding radio input is not that easy prior Angular RC 2. With the new form in RC 3 onward, we can directly bind to ngModel, bind the value property.

We have this list of genders:-

// app.component.ts

public genders = [
    { value: 'F', display: 'Female' },
    { value: 'M', display: 'Male' }
];

When select, we want only the value F or M.

<!-- app.component.html -->
...
<div>
    <label>Gender</label>
    <div *ngFor="let gender of genders">
        <label>
        <input type="radio" name="gender" [(ngModel)]="user.gender" 
        [value]="gender.value">
        {{gender.display}}
        </label>
    </div>
</div>
...

4. Select (Primitive type)

You can bind select to ngModel. Loop through your option list, set the value property. We have a list of roles:-

// app.component.ts

public roles = [
    { value: 'admin', display: 'Administrator' },
    { value: 'guest', display: 'Guest' },
    { value: 'custom', display: 'Custom' }
];

When value selected, we expected it to return string value admin, guest or custom. Here’s how your HTML will look like.

<!-- app.component.html -->
...
<div>
    <label>Role</label>
    <select name="role" [(ngModel)]="user.role">
        <option *ngFor="let role of roles" [value]="role.value">  
        {{role.display}}
        </option>
    </select>
</div>
...

5. Select (object)

Similar to last example, but this time, instead of simple type, we want the whole object when it’s selected. Here is the list of themes:-

// app.component.ts

public themes: Theme[] = [
    { backgroundColor: 'black', fontColor: 'white', display: 'Dark' },
    { backgroundColor: 'white', fontColor: 'black', display: 'Light' },
    { backgroundColor: 'grey', fontColor: 'white', display: 'Sleek' }
];

When selected, for example Light theme, we expect { backgroundColor: 'white', fontColor: 'black', display: 'Light' } to be returned. Instead of binding to the value property, we bind to ngValue property.

<!-- app.component.html -->
...
<div>
    <label>Theme</label>
    <select name="theme" [(ngModel)]="user.theme">
        <option *ngFor="let theme of themes" [ngValue]="theme">  
            {{theme.display}}
        </option>
    </select>
</div>

...

6. Multiple select

We can select more than 1 topics. E.g. when selecting game and tech, it should return ['game', 'tech'].

// app.component.ts

public topics = [
    { value: 'game', display: 'Gaming' },
    { value: 'tech', display: 'Technology' },
    { value: 'life', display: 'Lifestyle' },
];

Similar to select, but this time our model is array of string.

<!-- app.component.html -->
...
<div>
    <label>Topics</label>
    <select multiple name="topics" [(ngModel)]="user.topics">
        <option *ngFor="let topic of topics" [value]="topic.value">  
            {{topic.display}}
        </option>
    </select>
</div>
...

7. Checkbox (boolean)

By default, checkbox return boolean. Bind ngModel and define name attribute as usual.

<!-- app.component.html -->
...
<div>
    <label>
        <input type="checkbox" name="isActive"  [(ngModel)]="user.isActive">
        Is Active
    </label>
</div>

...

8. Checkbox (Toggle value)

In this case, we want to display a checkbox. Instead of boolean, we expecting value. When checked, it should return toggled, else return value untoggled.

This is the list of toggles:-

// app.component.ts

public toggles = [
    { value: 'toggled', display: 'Toggled' },
    { value: 'untoggled', display: 'UnToggled' },
];

First, we define a hidden input to bind to the real model. Then we create the checkbox input, handle the checked property and change event. Change event fire every time value change, and it has $event that we can read from.

In our case, we read the $event.target.checked to find out if the checkbox is checked, then update model value accordingly.

<!-- app.component.html -->
...
<div>
    <input type="hidden" name="toggle" [(ngModel)]="user.toggle">
    <div>
        <label>
            <input type="checkbox"
                [checked]="user.toggle === toggles[0].value" 
                (change)="$event.target.checked? (user.toggle =  toggles[0].value) : (user.toggle = toggles[1].value)">
            {{ toggles[0].display }}
        </label>
    </div>
</div>

...

Tips during development

During development, it’s good that you can visualize the value. Angular provided a very useful json Pipe.

<!-- app.component.html -->
...
<pre>{{your_form or control_name | json }}</pre>

...

Summary

That’s it. Hope it helps your journey in Angular 2. Happy coding!

Jecelyn Yeen

Coder. Diver. Board Game Lover.

Speak English, Mandarin, JavaScript, Typescript, C# and more.

Problem solver at @iflix.