Getting Started With Angular 2 Step by Step: 5 - Forms and Validation

| Comments

UPDATE (18th May 2017): This tutorial has been updated to the latest version of Angular (Angular 4). Note that the Angular team has re-branded the terms Angular 2 to Angular and Angular 1.x to AngularJS which are now the official names of these frameworks.

This is the fifth article on the Getting Started with Angular 2 Step by Step series if you missed the previous articles go and check them out!

Good morning! Time for some Angular 2 goodness! Yesterday you learned about Angular 2 Routing and today you are going to learn about how you can create forms and validate them in Angular 2.

Angular 2 Getting Started

The Code Samples

You can download all the code samples for this article from this GitHub repository.

Our Application Up Until Now

At this point in time we have developed a tiny Angular 2 application to learn more about people from the StarWars universe. We have two components, the PeopleListComponent that displays a list of people and the PersonDetailsComponent that displays more information about the character that you have selected.

We use Angular 2 routing to navigate between these two views, the list of people being our default view and the one that is rendered as soon as the application starts.

We Want to Save Our Own Data!

Up until now we’ve just been reading information and we are tired of that! We want to be able to write and save our own data.

In order to do that we are going to transform our PersonDetailsComponent into a form and add some validation on top to ensure that the information that we save is correct.

At the end of exercise, our details form should look something like this:

Angular 2 Forms and Validation

First Things First! We Need to Add The Forms Module to Our App!

From RC5 onwards we need to declare our module dependencies in NgModule and since we want to use the new forms API you’ll need to import it in our app.module.ts:

1
import { FormsModule } from '@angular/forms';

And specify it as a dependency via the imports property:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// imports

@NgModule({
  declarations: [
    AppComponent,
    PeopleListComponent,
    PersonDetailsComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,        // <===== HERE! :)
    HttpModule,
    AppRoutingModule,
  ],
  providers: [PeopleService],
  bootstrap: [AppComponent]
})
export class AppModule { }

Happily for us, because we bootstrapped our project with the Angular cli this has already been handled for us. The whole app.module.ts should already look like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';

import { AppComponent } from './app.component';
import { PeopleListComponent } from './people-list/people-list.component';
import { PeopleService } from './people.service';
import { PersonDetailsComponent } from './person-details/person-details.component';

import { AppRoutingModule } from "./app-routing.module";

@NgModule({
  declarations: [
    AppComponent,
    PeopleListComponent,
    PersonDetailsComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule,
    AppRoutingModule,
  ],
  providers: [PeopleService],
  bootstrap: [AppComponent]
})
export class AppModule { }

Ok! Now we can proceed!

Using a File Template Instead of an Inline Template

The first thing we’re going to do now that things are getting serious is to extract our PersonDetailsComponent template in its very own file. Yes you can do that!

As a starting point, I thought it was nice for you to see your template bindings beside your component API but now that we are going to be writing more HTML it is nice to have it separated in its own file and get syntax highlighting and better text editor support altogether (although depending on the editor you’re using you may get great HTML support in your TypeScript via this or this in the future).

How can we use a file template instead of an inline one?

The @Component decorator has a templateUrl property in addition to the template property that we’ve been using thus far. Taking advantage of that property we can tell Angular 2 about which HTML template we want to use with a particular component. Let’s extract our PersonDetailsComponent template into a person-details.component.html file:

1
2
3
4
5
6
7
8
  <section *ngIf="person">
    <h2>You selected: {{person.name}}</h2>
    <h3>Description</h3>
    <p>
      {{person.name}} weights {{person.weight}} and is {{person.height}} tall.
    </p>
  </section>
  <button (click)="gotoPeoplesList()">Back to peoples list</button>

And update our PersonDetailsComponent metadata:

1
2
3
4
5
6
7
8
9
// some imports

@Component({
  selector: 'app-person-details',
  templateUrl: './person-details.component.html',  // <=== HERE!
})
export class PersonDetailsComponent implements OnInit {
    // some code...
}

A Basic Form in Angular 2

Ok, now that we got that covered, let’s add an HTML form to our view. Angular 2 works perfectly with HTML5 standards so we are just going to create a vanilla HTML form to edit the properties that we have in our Person interface.

We update our template to look like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<!-- new syntax for ng-if -->
<section *ngIf="person">
  <section>
    <h2>You selected: {{person.name}}</h2>
    <h3>Description</h3>
    <p>
      {{person.name}} weights {{person.weight}} and is {{person.height}} tall.
    </p>
  </section>
  <section>
    <form>
      <div>
        <label for="name">Name: </label>
        <input type="text" name="name">
      </div>
      <div>
        <label for="weight">Weight: </label>
        <input type="number" name="weight">
      </div>
      <div>
        <label for="height">Height: </label>
        <input type="number" name="height">
      </div>
    </form>
  </section>

  <button (click)="gotoPeoplesList()">Back to peoples list</button>
<section>

Ok, now we have a basic form but we are not displaying anything. Using the stuff that we’ve learned thus far we could tie our form to our component’s person by using event and property bindings.

So, for instance, we could do the following:

1
2
3
4
5
6
7
8
<div>
    <label for="name">Name: </label>
    <input type="text"
           name="name"
           [value]="person.name"
           (change)="person.name = name.value"
           #name>
</div>

Where we use a [value] property binding to bind the component’s person.name property to the input element, and we use the (change) event binding to update our person’s name.

There’s something weird though in this particular example, and that’s the #name right there inside our input element. That’s what Angular 2 calls a template local variable and it’s often used to keep DOM element related references and logic out of our component code.

In this case, the #name variable refers to the input element itself. That’s why when Angular 2 evaluates the person.name = name.value expression our person model gets updated.

This feels like way too much work to bind a property from our component to our template and back, doesn’t it?… If only there was a better way… :)

ngModel and Angular 2 Two-Way Data Binding

Well there is! The ngModel data binding, very similar to AngularJS ng-model, let’s us establish a two-way data binding for a given property between the component and the template. A two-way data binding effectively syncs the value of a property between template and component forwarding changes in both directions.

The syntax is a little bit special and takes a little bit to get accustomed to:

1
2
3
4
<div>
    <label for="name">Name: </label>
    <input type="text" name="name" [(ngModel)]="person.name">
</div>

Hell yeah! You read it right: [(ngModel)]="person.name". Before you start cursing let’s spend a minute longer contemplating this piece of code…

We’ve learned that event bindings are one-directional bindings that go from the template to the underlying component and are represented in brackets (click). We’ve also learned that property bindings are one-directional bindings that go from the component to the template and are represented in square brackets [src]. Therefore if we want a two-way binding, we have something equivalent to an event binding plus a property binding, merge both syntaxes and you get the “banana-in-a-box” syntax of [(ngModel)]. Makes sense right?

The [(ngModel)] two-way data binding can indeed be decomposed into an event/property binding combo like this:

1
2
3
<input type="text" name="name"
  [ngModel]="model.name"
  (ngModelChange)="model.name = $event" >

In the case of normal DOM events the $event variable usually gives us access to the DOM event itself. In this particular case though, for the special (ngModelChange) custom event, it takes the value of the input being changed. If you want to learn more about ngModel and when it’s useful to decompose it take a look at the Angular 2 docs.

Ok! So now that we know that [(ngModel)] exists, we can use it in our form to create a two-way data binding for each one of the person properties (but for the id, you don’t want to change that):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
  <!-- new syntax for ng-if -->
<section *ngIf="person">
  <section>
    <h2>You selected: {{person.name}}</h2>
    <h3>Description</h3>
    <p>
      {{person.name}} weights {{person.weight}} and is {{person.height}} tall.
    </p>
  </section>
  <section>
    <form>
      <div>
        <label for="name">Name: </label>
        <input type="text" name="name" [(ngModel)]="person.name">
      </div>
      <div>
        <label for="weight">Weight: </label>
        <input type="number" name="weight" [(ngModel)]="person.weight">
      </div>
      <div>
        <label for="height">Height: </label>
        <input type="number" name="height" [(ngModel)]="person.height">
      </div>
    </form>
  </section>

  <button (click)="gotoPeoplesList()">Back to peoples list</button>
</section>

If you run the application in your browser (remember ng serve --open or ng s -o if you’re cool) you’ll be able to see how whenever you change the value in these inputs the changes are reflected instantly in the description.

A Review of Angular 2 Data Bindings

With the [(ngModel)] binding we have now covered all data bindings available to you in Angular 2. Let’s make a quick recap of them before we continue with forms and validation.

Angular 2 has support for these data-bindings:

  • Interpolation: One-way data binding from the component to the template. Let’s you display information from the component into the template. Example: {{person.name}}.
  • Property bindings: One-way data bindings from the component to the template. Let you bind data from the component into the template. You typically use them to bind component data to DOM element properties (like for instance the src property of an image). Example: <img [src]="person.imageUrl">.
  • Event bindings: One-way data bindings from the template to the component. Let’s you bind template events to the component. You typically use them to execute arbitrary code as a response to an interaction between the user and the user interface of your application. Example [click)="selectPerson(person)".
  • [(ngModel)]: Two-way data binding from the component to the template and vice versa. It syncs data both ways from the component to the template and back. You typically use it within form inputs. Example [(ngModel)]="person.name".

Adding Validation to Our Form

Now that we have a way to update a person’s details let’s add some validation to ensure that the data we’re introducting is legit before we save it. We are going to do the following:

  1. Make the name field required,
  2. Display a super helpful validation error message to the user whenever the field is empty
  3. Enable/disable the form submit button based on the validity of the inputs within the form

The way we track changes and the validity of an input in Angular 2 is through the same ngModel directive that we use for two-way data binding. By using this directive with an input we can obtain information about whether or not the user has done something with the input, wether or not the value has changed and even if it is invalid.

Let’s add the required attribute to the name input to mark it as a required bit of information (One can’t live without a name):

1
2
<label for="name">Name: </label>
<input type="text" name="name" required [(ngModel)]="person.name">

Angular 2 uses the name attribute (in this case name="name") to identify this particular input and keep track of its changes and validity.

The easiest way to visualize how Angular 2 tracks changes in our input is to see how it adds/removes css classes to the input based on it’s state.

If we update the input with the following snippet of code to display the DOM className property after the input and go to the browser, you’ll be able to appreciate how as you interact with the input different classes are added:

1
2
3
4
5
6
7
8
9
<label for="name">Name: </label>

<!-- 1. Added #name local template variable in the input element -->
<input type="text" name="name" required [(ngModel)]="person.name" #name>

<!-- 2. Added text to show the input element className -->
<p>
 input "name" class is: {{ name.className }}
</p>

So if you:

  • leave the input untouched it will have the: ng-untouched ng-pristine ng-valid classes
  • click inside then outside the input and it will be marked as visited and given the ng-touched class
  • type something and it will be marked as dirty and given the ng-dirty class
  • remove its whole content and it will be marked as invalid and given the ng-invalid class

Test it yourself!

We can take advantage of this feature to add some css styling to our inputs when they are valid and invalid. You can update the styles.css file to include the following styles:

1
2
3
4
5
6
7
8
9
10
11
12
// These styles are HEAVILY, HEAVILY inspired (STOLEN!!)
// from the Angular 2 docs

// valid and required show green
input.ng-valid[required] {
  border-left: 5px solid #42A948; /* green */
}

// invalid
input.ng-invalid {
  border-left: 5px solid #a94442; /* red */
}

This css file is linked within index.html. As such it represents the global styles for our application which are applied globally regardless of in which component you are in.

In the future you’ll learn that you can define component-level styles that are only applied within a component. Yey!

Back to the PersonDetailsComponent template we now want to display an error message whenever the user doesn’t type the required name. We can do that by creating a local template variable name and setting its value to ngModel. How can we do that? Although unintuitive, you do that by assigning the local template variable to ngModel like this. Update the previous snippet to the following:

1
2
<label for="name">Name: </label>
<input type="text" name="name" required [(ngModel)]="person.name" #name="ngModel">

You can think of it as a way to gain access to the directive that tracks the changes and validity of the input. Now that name holds the value of the ngModel directive, we can access its properties and find whether or not the input is in a valid state. We can use that information to toggle the visibility of an error message:

1
2
3
4
5
<label for="name">Name: </label>
<input type="text" name="name" required [(ngModel)]="person.name" #name="ngModel">
<div [hidden]="name.valid || name.pristine" class="error">
    Name is required my good sir/lady!
</div>

That we will style with this magic css:

1
2
3
4
5
.error{
  padding: 12px;
  background-color: rgba(255, 0, 0, 0.2);
  color: red;
}

Notice how using the property binding syntax we can bind any expression to the [hidden] DOM property. In this case we only hide the message when ngModel tells us that the input is valid or pristine (no reason to show the error if the user hasn’t even started editing the input).

Custom Validation FTW!

Let’s do another example of validation. This time for the input element tied to a person’s weight using the max and min HTML5 input attributes. Let’s say that our Star Wars figures shouldn’t weight less than a feather nor more than a Rancor. We’ll update our template as follows:

1
2
<label for="weight">Weight: </label>
<input type="number" name="weight" [(ngModel)]="person.weight" min=0 max=350 #weight="ngModel">

And now we add the validation messages for both constraints:

1
2
3
4
5
6
7
8
9
<div *ngIf="weight.errors && (weight.dirty || weight.touched)"
    class="error">
    <p [hidden]="!weight.errors.min">
      Weight must be higher than a feather's. {{weight.value}} is way too low.
    </p>
    <p [hidden]="!name.errors.max">
      Weight can't be higher than a Rancor's. {{weight.value}} is too high
    </p>
</div>

If you run the example again (remember ng serve --open) you’ll be able to verify that this new validation doesn’t work! Oh no! What do we do?

Angular 2 let’s you extend the built-in validation system with new validators of your own. Let’s create two new validators for the min and max attributes. The validators will be represented as directives which we can create with the aid of our mighty companion: The Angular CLI. You can think of a directive as a way to add custom behavior to DOM elements matching a specific selector.

Type the following:

1
2
PS> ng generate directive min-validator
# ng g d min-validator

This will create a new directive for us and register the directive into our AppModule. The new directive will look like this:

1
2
3
4
5
6
7
8
9
10
import { Directive } from '@angular/core';

@Directive({
  selector: '[appMinValidator]'
})
export class MinValidatorDirective {

  constructor() { }

}

We can change the selector to reflect the attribute that we want to tie to the validation logic (min):

1
2
3
4
5
6
7
8
9
10
import { Directive } from '@angular/core';

@Directive({
  // CSS selector for attributes
  selector: '[min]'
})
export class MinValidatorDirective {

  constructor() { }
}

In order to turn this new directive into a validator we need to implement the Validator interface which contains the validate method. This method will contain the validation logic that will compare the current value of the input with our attribute min value.

But before we can implement that logic we need to get a hold of the min value. We’ll do that taking advantage of the @Input decorator (if you missed previous articles in the series, you can use inputs to pass data into a component or directive) :

1
2
3
4
5
6
7
8
export class MinValidatorDirective {
  // new @Input here
  // it will get the min number from the attribute
  // For example 5 for <input min=5 ...
  @Input() min: number;

  constructor() { }
}

And now we implement the Validator interface with the validate method as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import { Validator, AbstractControl, ValidationErrors} from '@angular/forms';

@Directive({...})
export class MinValidatorDirective implements Validator {
  // new @Input here
  // it will get the min number from the attribute
  // For example 5 for <input min=5 ...
  @Input() min: number;

  constructor() { }

  // Define validation logic
  validate(control: AbstractControl): ValidationErrors {
    const currentValue = control.value;
    const isValid = currentValue >= this.min;

    // return errors as an object
    return isValid ? null : {
      min: {
        valid: false
      }
    };
  }
}

Ok! So now we have a custom validator directive for the min attribute ready to go. We have created the directive, tied it to the min attribute via the [min] selector, implemented the validation logic via the Validator interface, registered the directive into our AppModule so we can use it within our application but there’s one little thing that remains. We need to make Angular Validation’s system aware of this validator. We do this by registering the validator as a provider for the NG_VALIDATORS token inside the @Directive decorator metadata:

1
2
3
4
5
6
7
8
9
10
import { Validator, AbstractControl, ValidationErrors, NG_VALIDATORS} from '@angular/forms';

@Directive({
  selector: '[min]',
  // register validator in DI
  providers: [{provide: NG_VALIDATORS, useExisting: MinValidatorDirective, multi: true}]
})
export class MinValidatorDirective implements Validator {
    // code...
}

Now when the Angular validation system uses dependency injection to get a hold of all the available validators (represented by NG_VALIDATORS) in addition to the built-in validators it will also have access to this new custom validator.

You can now verify that the validation logic we wrote earlier for our weight input is now working. Test to write a weight of -100 and you’ll be greeted by an error message. Yippi!

Now we can repeat the steps for the max validator. Let’s be cool and do the shorthand with the Angular CLI:

1
PS> ng g d max-validator

And write the validator logic:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import { Directive, Input } from '@angular/core';
import { Validator, AbstractControl, ValidationErrors, NG_VALIDATORS } from '@angular/forms';

@Directive({
  selector: '[max]',
  // register validator in DI
  providers: [{provide: NG_VALIDATORS, useExisting: MaxValidatorDirective, multi: true}]
})
export class MaxValidatorDirective implements Validator {
  @Input() max: number;

  validate(control: AbstractControl): ValidationErrors {
    const currentValue = control.value;
    const isValid = currentValue <= this.max;

    // return errors as an object
    return isValid ? null : {
      max: {
        valid: false
      }
    };

  }

  constructor() { }

}

Tada! Now we have our very own custom validators for min and max, Great job!

Submitting Our Form

Ok! Let’s do a quick summary! Up to this point we have added some validation to the name input within our form so that if the user removes the name we will show an error because you are not supposed to do that with a required field. We’ve also added min and max validation to our weight attribute and some basic styles.

The next step is to actually try to save these changes. To that end, we are going to add a submit button inside our form:

1
2
3
4
5
6
7
8
  <section>
    <form>
      <!--- form inputs here ...--->

      <!-- add button -->
      <button type="submit">Save</button>
    </form>
  </section>

And we need to disable it when the form is invalid so that the user can’t save corrupted/invalid data, cause chaos, mayhem and destroy our beloved servers (although you and I know that you must have this type of validation in your service layer as well as in your client).

In order to do that we create yet another local template variable #personForm to get access to the actual form via the ngForm directive. We then use that variable to disable the button when the form is invalid:

1
2
3
4
5
6
7
8
  <section>
    <form #personForm="ngForm">
      <div>
          <!-- inputs ... -->
      </div>
      <button type="submit" [disabled]="!personForm.form.valid">Save</button>
    </form>
  </section>

Finally we set a submit event binding in the form so that we can save our person details whenever we submit the form:

1
2
3
4
5
6
7
8
  <section>
    <form (ngSubmit)="savePersonDetails()" #personForm="ngForm">
      <div>
          <!-- inputs ... -->
      </div>
      <button type="submit" [disabled]="!personForm.form.valid">Save</button>
    </form>
  </section>

The whole template now looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<section *ngIf="person">
  <!-- description -->
  <section>
    <h2>You selected: {{person.name}}</h2>
    <h3>Description</h3>
    <p>
      {{person.name}} weights {{person.weight}} and is {{person.height}} tall.
    </p>
  </section>

  <!-- form -->
  <section>
    <form (ngSubmit)="savePersonDetails()" #personForm="ngForm">
      <div>
        <label for="name">Name: </label>
        <input type="text" name="name" [(ngModel)]="person.name" required #name="ngModel">
        <div [hidden]="name.valid || name.pristine" class="error">
            Name is required my good sir/lady!
        </div>
      </div>
      <div>
        <label for="weight">Weight: </label>
        <input type="number" name="weight" [(ngModel)]="person.weight" min=0 max=350 #weight="ngModel">
        <div *ngIf="weight.errors && (weight.dirty || weight.touched)"
            class="error">
            <p [hidden]="!weight.errors.min">
              Weight must be bigger than a feather's. {{weight.value}} is way too low.
            </p>
            <p [hidden]="!weight.errors.max">
              Weight can't be bigger than a Rancor's. {{weight.value}} is too high
            </p>
        </div>
      </div>
      <div>
        <label for="height">Height: </label>
        <input type="number" name="height" [(ngModel)]="person.height">
      </div>
      <div>
        <label for="profession">Profession:</label>
        <select name="profession" [(ngModel)]="person.profession">
          <option *ngFor="let profession of professions" [value]="profession">{{profession}}</option>
        </select>
      </div>


      <button type="submit" [disabled]="!personForm.form.valid">Save</button>
    </form>
  </section>

  <button (click)="gotoPeoplesList()">Back to peoples list</button>
<section>

We just need to update our PersonDetailsComponent to be able to handle that submit event:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// imports 

@Component({
  selector: 'app-person-details',
  templateUrl: './person-details.component.html',
  styles: []
})
export class PersonDetailsComponent implements OnInit {
    // codes...

    savePersonDetails(){
        alert(`saved!!! ${JSON.stringify(this.person)}`);
    }
}

And now if you test everything that you’ve just done in the browser: Click on Luke Skywalker, change his name to Luke Vader (moahahaha), then save, you should be able to see your changes in an alert box.

Let’s update our component further so we can save this information with the help of our PeopleService.

Saving Information

This has nothing to do with forms and validation so I’ll just run wild like the wind over it without much detail. We are going to add a save method in our service and then we are going to save the changes we’ve done on a person in memory within our service.

We start by updating the PersonDetailsComponent:

1
2
3
4
5
// etc
export class PersonDetailsComponent implements OnInit {
    savePersonDetails(){
      this.peopleService.save(this.person);
    }

And then we update our PeopleService with the new save method:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import { Injectable } from '@angular/core';
import { Person } from './person';

const PEOPLE : Person[] = [
      {id: 1, name: 'Luke Skywalker', height: 177, weight: 70, profession: ''},
      {id: 2, name: 'Darth Vader', height: 200, weight: 100, profession: ''},
      {id: 3, name: 'Han Solo', height: 185, weight: 85, profession: ''},
    ];

@Injectable()
export class PeopleService{

  getAll() : Person[] {
    return PEOPLE.map(p => this.clone(p));
  }
  get(id: number) : Person {
    return this.clone(PEOPLE.find(p => p.id === id));
  }
  save(person: Person){
    let originalPerson = PEOPLE.find(p => p.id === person.id);
    if (originalPerson) Object.assign(originalPerson, person);
    // saved moahahaha
  }

  private clone(object: any){
    // hack
    return JSON.parse(JSON.stringify(object));
  }
}

If you are a very observant person you’ll see that I have added the clone method to this service. The purpose of this mighty teeny tiny method is to avoid sharing the same object references between the different components in the app so that we can simulate “saving” in a way more faithful to reality.

What’s With NgModel and NgForm?

If you are a little bit like me, you are probably slightly confused with the ngModel and ngForm directives. So let’s do some recap about them:

ngModel

  • ngModel lets you track the state and validity of your inputs
  • ngModel adds css classes to your inputs based on their state, whether they have been touched, changed or whether they are valid or not.
  • Using #name="ngModel" in an input element creates a local template variable called #name and assigns the ngModel directive to it. You can then use the variable to access the ngModel directive properties like valid, pristine, touched, etc.

ngForm

  • Angular 2 attaches an NgForm directive to every form element.
  • The ngForm directive exposes the form.valid property that let’s you know if all controls within a given form are in a valid state.

ngModel and ngForm

  • Whenever you add the ngModel directive to an input Angular 2 is going to register it using that name that you provide (remember name="name") with an NgForm directive that Angular 2 automagically attaches to any form element.
  • The ngForm directive contains a collection of the controls created using the ngModel directive.

Hope that makes it more clear! If you want to learn more about the ngForm directive then you may want to take a look at the Angular 2 docs

Bonus Exercise. Adding a Select Input in Angular 2

As a bonus exercise let’s try to add a select input with Angular 2 to select the profession of our Star Wars figures. An HTML5 select element, also known as dropdown or listbox in UI circles typically has a select element that wraps a collection of option elements.

We will start by adding a profession to the Person interface:

1
2
3
4
5
6
7
8
9
10
11
export interface Person {
  id: number;
  name: string;
  height: number;
  weight: number;

  // it is optional because I know it
  // doesn't exist in the API that we will
  // consume in the next exercise :)
  profession?: string;
}

Then we update our PersonDetailsComponent to include all the available professions:

1
2
3
4
export class PersonDetailsComponent implements OnInit {
    professions: string[] = ['jedi', 'bounty hunter', 'princess', 'sith lord'];
    // other code
}

And finally we update the PersonDetailsComponent template to include the select element:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!-- description header ... -->
  <section>
    <form (ngSubmit)="savePersonDetails()" #personForm="ngForm">
      <div>
      <! -- old inputs ... -->
      <!-- ...and the new select element -->
      <div>
        <label for="profession">Profession:</label>
        <select name="profession" [(ngModel)]="person.profession">
          <option *ngFor="let profession of professions" [value]="profession">{{profession}}</option>
        </select>
      </div>
      <button type="submit" [disabled]="!personForm.form.valid">Save</button>
    </form>
  </section>

And that’s it! You can now go back to the form, click on Luke Skywalker, make him a LORD SITH, save, and who’s gonna know what’s gonna happpen in the next Star Wars movie.

Do You Want to Learn More About Forms?

Would you like to learn more about Angular 2 forms, then check any of these articles:

And by the by, if you haven’t checked it yet, take a look at this awesome course on Angular 2 at Pluralsight: Angular 2: First Look by the one and only John Papa

Concluding

Great! We have got to the end of yet another article. Now you can coun forms and validation as one more of your Angular 2 ninja skills.

Up next! Consuming a real service using Angular 2 Http.

An Aside: Oh No! I Haven’t Had Time To Update To The New Forms API! Can I Update to RC5?

If you haven’t had time to update to the new forms API but still want to start using RC5, you can enable the old forms by importing the DeprecatedFormsModule and adding it to your app.module.ts:

1
2
3
4
5
6
7
8
import {DeprecatedFormsModule} from @angular/common;

@NgModule({
  imports: [ BrowserModule, routing, DeprecatedFormsModule ],
  declarations: [ AppComponent, PeopleListComponent, PersonDetailsComponent],
  bootstrap: [ AppComponent ]
})
export class AppModule { }

Comments