Angular 2 Forms

David East / @_davideast

Full-time Firebase. Part-time Angular.

Disclaimer

Angular 2 is a work in progress.

Things are always changing.

Outline

Angular 2 Crash Course

Why forms are hard

Overview of Angular 2 Forms

Code

Angular 2 Crash Course

Angular 2

The framework itself is written in TypeScript

Your app can use either ES5 of TypeScript

Components

Break the UI down into resuable pieces

            
@Component({
  selector: 'todo-app',
})
@View({
  url: 'todos.html'
})
class TodoApp {
  todos: [string];
  constructor() {
    this.todos = ['Item1', 'Item2'];
  }
}
            
          
            
<todo-app></todo-app>
            
          

Annotations

Customize components

            
@Component({
  selector: 'todo-app',
})
@View({
  url: 'todos.html'
})
class TodoApp {

}
            
          

ES5 Annotations

Customize components

            
// Constructor function
function TodoApp() { }

// Annotations attached to function
TodoApp.annotations = [
  new Component({ selector: 'todo-app' }),
  new View({ url: 'todos.html' })
];
            
          

Controller

The backing of the component's view

            
class TodoApp {
  todos: [string];

  constructor() {
    this.todos = ['Item1', 'Item2'];
  }

  addTodo(title: string) {
    this.todos.push(title);
  }
}
            
          

ES5 Controller

The backing of the component's view

            
function TodoApp() {
  this.todos = ['Item1', 'Item2'];
}
TodoApp.prototype.addTodo = function(title) {
  this.todos.push(title);
};
            
          

Template Syntax

Consists of three main features

Local Variables - #localvar

Event Handlers - (click)

Property Bindings - [innerText]

Template Syntax

            
<!-- # creates a local variable inside the template -->
<input #textbox>

<!-- (event) registers an event handler -->
<button (click)="addTodo(textbox.value)"> Add </button>

<!-- [property] binds a value to a property of a DOM element or component -->

Angular 2 Components

@Component, @View, Controller

            
@Component({
  selector: 'todo-app',
})
@View({
  url: 'todos.html'
})
class TodoApp {
  todos: [string];
  constructor() {
    this.todos = ['Item1', 'Item2'];
  }
}
            
          

Angular 2 Forms

Forms are one of our few outlets to the user

Never trust the user

An attempt to hack a bank account from a login form.

Forms are hard

Complex logic

Testing

Evaluating changes over time

Complex Logic

One control can be dependent on the logic of another

A user can answer a form in an adhoc manner

The user is constantly triggering different error states

Testing

The template needs to be compiled

Which can lead to many e2e tests

This makes fast & isolated tests impossible

Changes

Waiting on a response back from the server

Evaluating form input on keyup or change

Changes triggering updates on dependent controls

Angular 2 Forms

External module of Angular, like the router

Not required, but useful

Angular 2 Forms

Imperative Driven

Data Driven

Template Driven

Angular 2 Forms

Complex logic - Handled with Controls and Validators

Testing - Isolated with DOM independent Controls

Evaluating Changes - Simplified with Observables

Controls and Validators for Complex Logic

Control

Smallest piece of Angular forms

Represents a single form input

Control API

            
// create a control
var nameControl = new Control("David");

// store its value
var nameValue = nameControl.value;

// check control states
var isValid = nameControl.valid;
var hasBeenChanged = nameControl.dirty;
var hasNotBeenChanged = nameControl.pristine;
var numErrors = nameControl.errors.length;
            
          

Control Group

A collection of controls

Maintains the overall state of the form

Control Group

            
// create a control group
var group = new ControlGroup({
  fullName: new Control("David East", Validators.required),
  username: new Control("ng2rox", Validators.required),
  favColor: new Control("")
});
            
          

Control Group API

            
 // same as previous slide
var group = new ControlGroup({ ... });

var isValid = group.valid;
var hasBeenChanged = group.dirty;
var hasNotBeenChanged = group.pristine;
var numErrors = group.errors.length;
            
          

Form Builder API

Builder class that provides a short form API

            
// create a builder
var builder = new FormBuilder();

// build a group w/ controls
var group = builder.group({
  fullName: ["David East", Validators.required],
  username: ["ng2rox", Validators.required],
  favColor: ["Blue"]
});
            
          

Validators

Functions that encapsulate custom form logic

Validators

Return null if valid

Return an object literal of errors if invalid

            
function notBlank(c: Control) {
  if(c.value === "") {
    return {
      notBlank: true
    }
  }
  return null;
}

var control = new Control("Hello", notBlank);
            
          
            
import {bootstrap, Component, View} from 'angular2/angular2';
import {FormBuilder, Validators, FormDirectives, ControlGroup} from 'angular2/forms';

@Component({
  selector: 'form-app'
})
@View({
  url: 'form-app.html',
  directives: [FormDirectives]
})
class FormApp {
  myForm: ControlGroup;

  constructor(builder: FormBuilder) {
    this.myForm = builder.group({
      fullName: ["", Validators.required],
      username: ["", Validators.required],
      favColor: [""]
    });
  }

}
            
          

Isolated testing with Controls

Testing

Isolated and DOM independent testing

            
var builder = new FormBuilder();
var app = new AppComponent(builder);

// The AppComponent is initially in an invalid state

// Assertion passes
console.assert(app.myForm.valid === false);

// Assertion fails
console.assert(app.myForm.valid === true);
            
          

Template Syntax

The [control-group] propery binding declares the form for the view

            
<div [control-group]="myForm">

</div>
            
          

The [control] property binding binds the control to the input

            
<div [control-group]="myForm">

  <input [control]="myForm.controls.fullName">

</div>
            
          

The control decorator provides a shortcut for binding the control to the input

            
<div [control-group]="myForm">

  <!-- myForm.controls.fullName-->
  <input control="fullName">

</div>
            
          

Nested Groups

A control group can contain other control groups

Nested Groups

            
// create a builder
var builder = new FormBuilder();

// build a group w/ controls
var group = builder.group({
  fullName: ["David East", Validators.required],
  username: ["ng2rox", Validators.required],

  // nested group
  address: builder.group({
    street: ["", Validators.required],
    streetLine2: [""],
    city: ["", Validators.required],
    state: ["", Validators.required],
    zip: ["", Validators.required]
  });
});

var isValid = group.controls.address.valid; // is the address valid?
            
          

Example Template

            
<div [control-group]="myForm">

  <input control="fullName">
  <input control="username">

  <!-- Nested Control Group -->
  <div control-group="address">

    <input control="street">
    <input control="streetLine2">
    <input control="city">
    <input control="state">
    <input control="zip">

  </div>

</div>
            
          

Observables

Evaluate changes as they arrive over time

            
var control = new Control();
var ref = new Firebase('https://ngforms.firebaseio.com/control')

// Save updates to Firebase
control.valueChanges.subscribe(function(value) {
  ref.set(value)
});
            
          

Code

Conclusion

Complex logic - Handled with Controls and Validators

Testing - Isolated with DOM independent Controls

Evaluating Changes - Simplified with Observables

The End

David East / @_davideast