exhtml
6/4/2018 - 5:15 AM

Angular forms manage different form inputs

// TEXT INPUT

// We use two-way data binding with 'ngModel' to bind to the 'textValue' property 
// on the component class. Two-way binding allows us to use the property to set 
// the initial value of an '<input>', and then have the user's changes flow back to the property. 

// text.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-text-box',
  template: `
        <h1>Text ({{textValue}})</h1>
        <input #textbox type="text" [(ngModel)]="textValue" required> <!-- A template reference variable on the input... -->
        <button (click)="logText(textbox.value)">Update Log</button> <!-- ...gives us access to the properties and methods of the input control. -->
        <button (click)="textValue=''">Clear</button>

        <h2>Template Reference Variable</h2>
        Type: '{{textbox.type}}', required: '{{textbox.hasAttribute('required')}}',
        upper: '{{textbox.value.toUpperCase()}}'

        <h2>Log <button (click)="log=''">Clear</button></h2>
        <pre>{{log}}</pre>`
})
export class TextComponent {

  textValue = 'initial value';
  log = '';

  logText(value: string): void {
    this.log += `Text changed to '${value}'\n`;
  }
}

// CHECKBOX

// checkbox.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-checkbox',
  template: `
        <h1>Checkbox</h1>
        <p>
            <label [class.selected]="cb1.checked"> <!-- we can determine whether or not the checkbox has been selected using the 'checked' property and highlight the selected checkbox labels using class bindings. -->
                <input #cb1 type="checkbox" value="one" (change)="logCheckbox(cb1)"> One <!-- 'change' event is triggered when the user clicks on a checkbox. We use this event to pass a template reference variable to the function -->
            </label>
            <label [class.selected]="cb2.checked">
                <input #cb2 type="checkbox" value="two" (change)="logCheckbox(cb2)"> Two
            </label>
            <label [class.selected]="cb3.checked">
                <input #cb3 type="checkbox" value="three" (change)="logCheckbox(cb3)"> Three
            </label>
        </p>

        <h2>Log <button (click)="log=''">Clear</button></h2>
        <pre>{{log}}</pre>`,
  styles: ['.selected {color: OrangeRed;}']
})
export class CheckboxComponent {

  log = '';

  logCheckbox(element: HTMLInputElement): void {
    this.log += `Checkbox ${element.value} was ${element.checked ? '' : 'un'}checked\n`;
  }
}

// RADIO

// radio.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-radio',
  template: `
        <h1>Radio</h1>
        <p>
            <label [class.selected]="r1.checked">
                <input #r1 type="radio" name="r" value="one" (change)="logRadio(r1)"> One
            </label>
            <label [class.selected]="r2.checked">
                <input #r2 type="radio" name="r" value="two" (change)="logRadio(r2)"> Two
            </label>
            <label [class.selected]="r3.checked">
                <input #r3 type="radio" name="r" value="three" (change)="logRadio(r3)"> Three
            </label>
        </p>

        <h2>Log <button (click)="log=''">Clear</button></h2>
        <pre>{{log}}</pre>`,
  styles: ['.selected {color: OrangeRed;}']
})
export class RadioComponent {

  log = '';

  logRadio(element: HTMLInputElement): void {
    this.log += `Radio ${element.value} was selected\n`;
  }
}

// COMBO / DROPDOWN LIST

// drop-down.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-drop-down',
  template: `
        <h1>Drop-down List</h1>
        <select #select [(ngModel)]="current" (change)="logDropdown(select.value)">
            <option *ngFor="let item of list" [value]="item.id">{{item.name}}</option>
        </select>

        <h2>Log <button (click)="log=''">Clear</button></h2>
        <pre>{{log}}</pre>`
})
export class DropDownComponent {

  list: any = [
    {id: 1, name: 'one'},
    {id: 2, name: 'two'},
    {id: 3, name: 'three'}
  ];
  current = 2;
  log = '';

  logDropdown(id: number): void {
    const NAME = this.list.find((item: any) => item.id === +id).name;
    this.log += `Value ${NAME} was selected\n`;
  }
}

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;
}

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; // en lugar de models usa interfaces, por que?
    
    /* standing data (ver en cada caso de cada input) */

    ngOnInit() {
        // initialize user model
      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] // [] MAKES THE FIELD AN ARRAY //default to Technology
      }
    }

    public save(isValid: boolean, f: User) {
        console.log(f);
    }
}

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>

Text / Number input

You need the name attribute, and ngModel.

<div>
    <label>Name</label>
    <input type="text" name="name" [(ngModel)]="user.name">
</div>
<div>
    <label>Age</label>
    <input type="number" name="age" [(ngModel)]="user.age">
</div>

Radio button

We have this list of genders:

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

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

<div>
  <label>Gender</label>
  <div *ngFor="let gender of genders"> 
    <label>
      <input type="radio" name="gender" 
             [(ngModel)]="user.gender" //bindeamos con la propiedad del model que queramos
             [value]="gender.value"> //componemos las opciones a partir del array de 'genders'
      {{gender.display}}
    </label>
  </div>
</div>

Select

1. Primitive type

We have a list of roles:

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:

<div>
    <label>Role</label>
    <select name="role" [(ngModel)]="user.role"> //bindeamos con la propiedad del model que queramos
        <option *ngFor="let role of roles" [value]="role.value"> //componemos las opciones a partir del array de 'roles' 
        {{role.display}}
        </option>
    </select>
</div>

2. Object

Instead of simple type, we want the whole object when it’s selected.
We have the list of themes:

public themes: Theme[] = [ //array de elementos del tipo Theme (porqué lo usa así en lugar de con models?)
    { 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.

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

Select multiple

when selecting game and tech, it should return ['game', 'tech'].

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: topics: []

<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>

Checkbox

1. Boolean

By default, checkbox return boolean.

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

2. Toggle value

Instead of boolean, we expecting value. When checked, it should return toggled, else return value untoggled.

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

How we do it:

<div>
    <input type="hidden" name="toggle" [(ngModel)]="user.toggle"> // we define a hidden input to bind to the real model
    <div>
      <label>
        <input type="checkbox" 
               [checked]="user.toggle === toggles[0].value"  //handle the 'checked' property
               (change)="$event.target.checked ? (user.toggle = toggles[0].value) : (user.toggle = toggles[1].value)"> // 'change' event fire every time value change: we read the $event.target.checked to find out if the checkbox is checked, then update model value accordingly
            {{ toggles[0].display }}
      </label>
    </div>
</div>

During development, visualize the values: <pre>{{your_form or control_name | json }}</pre>