Forms

Send feedback

We’ve all used a form to log in, submit a help request, place an order, book a flight, schedule a meeting, and perform countless other data entry tasks. Forms are the mainstay of business applications.

Any seasoned web developer can slap together an HTML form with all the right tags. It's more challenging to create a cohesive data entry experience that guides the user efficiently and effectively through the workflow behind the form.

That takes design skills that are, to be frank, well out of scope for this chapter.

It also takes framework support for two-way data binding, change tracking, validation, and error handling ... which we shall cover in this chapter on Angular forms.

We will build a simple form from scratch, one step at a time. Along the way we'll learn:

  • How to build an Angular form with a component and template

  • The ngModel two-way data binding syntax for reading and writing values to input controls

  • The ngControl directive to track the change state and validity of form controls

  • The special CSS classes that ngControl adds to form controls and how to use them to provide strong visual feedback

  • How to display validation errors to users and enable/disable form controls

  • How to share information across controls with template reference variables

Run the .

Template-driven forms

Many of us will build forms by writing templates in the Angular template syntax with the form-specific directives and techniques described in this chapter.

That's not the only way to create a form but it's the way we'll cover in this chapter.

We can build almost any form we need with an Angular template—login forms, contact forms, pretty much any business form. We can lay out the controls creatively, bind them to data, specify validation rules and display validation errors, conditionally enable or disable specific controls, trigger built-in visual feedback, and much more.

It will be pretty easy because Angular handles many of the repetitive, boilerplate tasks we'd otherwise wrestle with ourselves.

We'll discuss and learn to build a template-driven form that looks like this:

Clean Form

Here at the Hero Employment Agency we use this form to maintain personal information about the heroes in our stable. Every hero needs a job. It's our company mission to match the right hero with the right crisis!

Two of the three fields on this form are required. Required fields have a green bar on the left to make them easy to spot.

If we delete the hero name, the form displays a validation error in an attention-grabbing style:

Invalid, Name Required

Note that the submit button is disabled, and the "required" bar to the left of the input control changed from green to red.

We'll customize the colors and location of the "required" bar with standard CSS.

We'll build this form in small steps:

  1. Create the Hero model class.
  2. Create the component that controls the form.
  3. Create a template with the initial form layout.
  4. Add the ngModel directive to each form input control.
  5. Add the ngControl directive to each form input control.
  6. Add custom CSS to provide visual feedback.
  7. Show and hide validation error messages.
  8. Handle form submission with ngSubmit.
  9. Change the form's display after submission.

Setup

Create a new project folder (angular_forms) and create 3 files: pubspec.yaml, web/index.html, and web/main.dart. (These files should be familiar from the QuickStart.) Put these contents in the files:

name: hero_form description: Form example version: 0.0.1 environment: sdk: '>=1.19.0 <2.0.0' dependencies: angular2: ^2.2.0 dev_dependencies: browser: ^0.10.0 dart_to_js_script_rewriter: ^1.0.1 transformers: - angular2: platform_directives: - 'package:angular2/common.dart#COMMON_DIRECTIVES' platform_pipes: - 'package:angular2/common.dart#COMMON_PIPES' entry_points: web/main.dart - dart_to_js_script_rewriter <!DOCTYPE html> <html> <head> <title>Hero Form</title> <script defer src="main.dart" type="application/dart"></script> <script defer src="packages/browser/dart.js"></script> </head> <body> <hero-form>Loading...</hero-form> </body> </html> import 'package:angular2/platform/browser.dart'; import 'package:hero_form/hero_form_component.dart'; main() { bootstrap(HeroFormComponent); }

So that the code can run, let's create a stub for the <hero-form> component.

Create a new directory called lib. In it, put a file called hero_form_component.dart with the following code:

lib/hero_form_component.dart

import 'package:angular2/core.dart'; @Component(selector: 'hero-form', template: 'Hero form will go here') class HeroFormComponent {}

The app should now run, but it won't do anything interesting. Let's add some data.

Create the Hero model class

As users enter form data, we'll capture their changes and update an instance of a model. We can't lay out the form until we know what the model looks like.

A model can be as simple as a "property bag" that holds facts about a thing of application importance. That describes well our Hero class with its three required fields (id, name, power) and one optional field (alterEgo).

In the lib directory, add a file called hero.dart with the following code:

lib/hero.dart

class Hero { int number; String name; String power; String alterEgo; Hero(this.number, this.name, this.power, [this.alterEgo]); String toString() => '$number: $name ($alterEgo). Super power: $power'; }

It's an anemic model with few requirements and no behavior. Perfect for our demo.

The alterEgo is optional, so the constructor lets us omit it: note the [] in [this.alterEgo].

We can create a new hero like this:

var myHero = new Hero( 42, 'SkyDog', 'Fetch any object at any distance', 'Leslie Rollover'); print('My hero is ${myHero.name}.'); // "My hero is SkyDog."

Create a form component

An Angular form has two parts: an HTML-based template and a code-based component to handle data and user interactions.

We begin with the component because it states, in brief, what the Hero editor can do.

Edit hero_form_component.dart, replacing all of its contents with the following code:

lib/hero_form_component.dart

import 'package:angular2/core.dart'; import 'hero.dart'; const List<String> _powers = const [ 'Really Smart', 'Super Flexible', 'Super Hot', 'Weather Changer' ]; @Component( selector: 'hero-form', templateUrl: 'hero_form_component.html') class HeroFormComponent { List<String> get powers => _powers; bool submitted = false; Hero model = new Hero(18, 'Dr IQ', _powers[0], 'Chuck Overstreet'); // TODO: Remove this when we're done String get diagnostic => 'DIAGNOSTIC: $model'; onSubmit() { submitted = true; } }

There’s nothing special about this component, nothing form-specific, nothing to distinguish it from any component we've written before.

Understanding this component requires only the Angular concepts covered in previous chapters.

  1. The code imports a standard set of symbols from the Angular library.

  2. The @Component selector value of "hero-form" means we can drop this form in a parent template with a <hero-form> tag.

  3. The templateUrl property points to a separate file for template HTML called hero_form_component.html.

  4. We defined dummy data for model and powers, as befits a demo. Down the road, we can inject a data service to get and save real data or perhaps expose these properties as inputs and outputs for binding to a parent component. None of this concerns us now, and these future changes won't affect our form.

  5. We threw in a diagnostic property to return a string describing our model. It'll help us see what we're doing during our development; we've left ourselves a cleanup note to discard it later.

Why isn't the template inline in the component file?

Inline templates can be nice when they are short, but most form templates aren't short. Dart files generally aren't the best place to write (or read) large stretches of HTML, and few editors are much help with files that have a mix of HTML and code. It's also nice to have short files with a clear and obvious purpose.

We made a good choice to put the HTML template elsewhere. Let's write it.

Create an initial HTML form template

Create a new file under lib called hero_form_component.html, and put the following template code in it:

lib/hero_form_component.html

<div class="container"> <h1>Hero Form</h1> <form> <div class="form-group"> <label for="name">Name</label> <input type="text" class="form-control" required> </div> <div class="form-group"> <label for="alterEgo">Alter Ego</label> <input type="text" class="form-control"> </div> <button type="submit" class="btn btn-default">Submit</button> </form> </div>

That is plain old HTML 5. We're presenting two of the Hero fields, name and alterEgo, and opening them up for user input in input boxes.

The Name <input> control has the HTML5 required attribute; the Alter Ego <input> control does not because alterEgo is optional.

We've got a Submit button at the bottom with some classes on it.

We are not using Angular yet. There are no bindings. No extra directives. Just layout.

The container,form-group, form-control, and btn classes are Bootstrap CSS. Purely cosmetic. We're using Bootstrap to gussy up our form. Hey, what's a form without a little style!

Angular forms do not require a style library

Angular makes no use of the container, form-group, form-control, and btn classes or the styles of any external library. Angular apps can use any CSS library ... or none at all.

Let's add the stylesheet.

  1. Download the Bootstrap stylesheet from https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css, and put it in the web directory.

  2. Edit web/index.html, adding a link to bootstrap.min.css:

web/index.html (excerpt)

<link rel="stylesheet" href="bootstrap.min.css"> <script defer src="main.dart" type="application/dart"></script> <script defer src="packages/browser/dart.js"></script>

Add powers with *ngFor

Our hero must choose one super power from a fixed list of Agency-approved powers. We maintain that list internally (in HeroFormComponent).

We'll add a select to our form and bind the options to the powers list using ngFor, a technique used before in Displaying Data.

Add the following HTML immediately below the Alter Ego group.

lib/hero_form_component.html (excerpt)

<div class="form-group"> <label for="power">Hero Power</label> <select class="form-control" required> <option *ngFor="let p of powers" [value]="p">{{p}}</option> </select> </div>

This code repeats the <option> tag for each power in the list of powers. The p template input variable is a different power in each iteration; we display its name using the interpolation syntax with the double curly braces.

Two-way data binding with *ngModel

Running the app right now would be disappointing.

Early form with no binding

We don't see hero data because we are not binding to the Hero yet. We know how to do that from earlier chapters. Displaying Data taught us property binding. User Input showed us how to listen for DOM events with an event binding and how to update a component property with the displayed value.

Now we need to display, listen, and extract at the same time.

We could use the techniques we already know, but instead we'll introduce something new: the NgModel directive, which makes binding the form to the model super easy.

Find the <input> tag for Name and update it like this:

lib/hero_form_component.html (excerpt)

<input type="text" class="form-control" required [(ngModel)]="model.name" > TODO: remove this: {{model.name}}

We added a diagnostic interpolation after the input tag so we can see what we're doing. We left ourselves a note to throw it way when we're done.

Focus on the binding syntax: [(ngModel)]="...".

If we ran the app right now and started typing in the Name input box, adding and deleting characters, we'd see them appearing and disappearing from the interpolated text. At some point it might look like this.

ngModel in action

The diagnostic is evidence that values really are flowing from the input box to the model and back again. That's two-way data binding!

Let's add similar [(ngModel)] bindings to Alter Ego and Hero Power. We'll ditch the input box binding message and add a new binding at the top to the component's diagnostic property. Then we can confirm that two-way data binding works for the entire Hero model.

After revision, the core of the form should have three [(ngModel)] bindings that look much like this:

lib/hero_form_component.html (excerpt)

{{diagnostic}} <div class="form-group"> <label for="name">Name</label> <input type="text" class="form-control" required [(ngModel)]="model.name" > </div> <div class="form-group"> <label for="alterEgo">Alter Ego</label> <input type="text" class="form-control" [(ngModel)]="model.alterEgo"> </div> <div class="form-group"> <label for="power">Hero Power</label> <select class="form-control" required [(ngModel)]="model.power"> <option *ngFor="let p of powers" [value]="p">{{p}}</option> </select> </div>

If we ran the app right now and changed every Hero model property, the form might look like this:

ngModel in super action

The diagnostic near the top of the form confirms that our changes to the values are reflected in the model.

Delete the diagnostic binding. It has served its purpose.

Inside [(ngModel)]

This section is an optional deep dive into [(ngModel)]. Not interested? Skip ahead!

The punctuation in the binding syntax, [()], is a good clue to what's going on.

A property binding makes data flow from the model to a target property on screen. We identify that target property by surrounding its name in brackets, []. This is a one-way data binding from the model to the view.

An event binding makes data flow from the target property onscreen to the model. We identify that target property by surrounding its name in parentheses, (). This is a one-way data binding in the opposite direction from the view to the model.

No wonder Angular uses the combined punctuation, [()], to signify a two-way data binding and a flow of data in both directions.

In fact, we can break the NgModel binding into its two separate modes as in this rewrite of the Name <input> binding:

lib/hero_form_component.html (excerpt)

<input type="text" class="form-control" required [ngModel]="model.name" (ngModelChange)="model.name = $event" > TODO: remove this: {{model.name}}


The property binding should feel familiar. The event binding might seem strange.

The name ngModelChange specifies an event property of the NgModel directive. When Angular sees a binding target in the form [(x)], it expects the x directive to have an x input property and an xChange output property.

The other oddity is the template expression, model.name = $event. We're used to seeing an $event object coming from a DOM event. The ngModelChange property doesn't produce a DOM event; it's an Angular EventEmitter property that returns the input box value when it fires—which is precisely what we should assign to the model's name property.

Nice to know but is it practical? [(ngModel)] is usually what we want, but we might split the binding when the event handling has to do something special such as debounce or throttle the keystrokes.

Track change-state and validity with ngControl

A form isn't just about data binding. We'd also like to know the state of the controls on our form. The NgControl directive keeps track of control state for us.

NgControl requires Form

The NgControl is one of a family of NgForm directives that can only be applied to a control within a <form> tag.

Our application can ask an NgControl instance whether the user touched the control, the value changed, or the value is valid.

NgControl doesn't just track state; it updates the control with special Angular CSS classes, such as ng-valid or ng-invalid. We can use those class names to change the appearance of the control and make messages appear or disappear.

We'll explore those effects soon. Right now let's add ngControlto all three form controls, starting with the Name input box.

lib/hero_form_component.html (excerpt)

<input type="text" class="form-control" required [(ngModel)]="model.name" ngControl="name" >

Be sure to assign a unique name to each ngControl directive.

Angular registers controls under their ngControl names with the NgForm directive. We didn't add the NgForm directive explicitly but it's here; we'll talk about it later in this chapter.

Add custom CSS for visual feedback

NgControl doesn't just track state. It updates the control to have three classes that describe the control's state.

State Class if true Class if false
Control has been visited ng-touched ng-untouched
Control's value has changed ng-dirty ng-pristine
Control's value is valid ng-valid ng-invalid

Let's add a temporary template reference variable named spy to the "Name" <input> tag and use the spy to display those classes.

lib/hero_form_component.html (excerpt)

<input type="text" class="form-control" required [(ngModel)]="model.name" ngControl="name" #spy > TODO: remove this: {{spy.className}}

Now run the app, and look at the Name input box. Follow the next four steps precisely:

  1. Look but don't touch.
  2. Click inside the name box, then click outside it.
  3. Add slashes to the end of the name.
  4. Erase the name.

The classes are displayed as follows:

  1. form-control ng-untouched ng-valid ng-pristine (initial state)
  2. form-control ng-valid ng-pristine ng-touched (after clicking)
  3. form-control ng-valid ng-touched ng-dirty (after changing)
  4. form-control ng-touched ng-dirty ng-invalid (after erasing)
Control State Transition

The (ng-valid | ng-invalid) pair are the most interesting to us, because we want to send a strong visual signal when the values are bad. We also want to mark required fields.

We can do both at the same time with a colored bar on the left of the input box:

Invalid Form

We achieve this effect by adding two styles to a new forms.css file (under web/).

web/forms.css

.ng-valid[required] { border-left: 5px solid #42A948; /* green */ } .ng-invalid { border-left: 5px solid #a94442; /* red */ }

These styles select for the two Angular validity classes and the HTML 5 "required" attribute.

To add the styles to the app, update the <head> of index.html to link to forms.css.

web/index.html (excerpt)

<link rel="stylesheet" href="bootstrap.min.css"> <link rel="stylesheet" href="styles.css"> <link rel="stylesheet" href="forms.css">

Show and hide validation error messages

We can do better.

The Name input box is required. Clearing it turns the bar red. That says something is wrong but we don't know what is wrong or what to do about it. We can leverage the ng-invalid class to reveal a helpful message.

Here's the way it should look when the user deletes the name:

Name required

To achieve this effect we extend the <input> tag with

  1. a template reference variable
  2. the "is required" message in a nearby <div> which we'll display only if the control is invalid.

Here's an example of adding an error message to the "name" input box:

lib/hero_form_component.html (excerpt)

<input type="text" class="form-control" required [(ngModel)]="model.name" ngControl="name" #name="ngForm" > <div [hidden]="name.valid" class="alert alert-danger"> Name is required </div>

We need a template reference variable to access the input box's Angular control from within the template. Here we created a variable called name and gave it the value "ngForm".

Angular recognizes that syntax and sets the name variable to the Control object identified by the ngControl directive that, not coincidentally, we called "name".

We bind the Control object's valid property to the element's hidden property. While the control is valid, the message is hidden; if it becomes invalid, the message is revealed.

The NgForm directive

Recall from the previous section that ngControl registered this input box with the NgForm directive as "name".

We didn't add the NgForm directive explicitly. Angular added it surreptitiously, wrapping it around the <form> element.

The NgForm directive supplements the <form> element with additional features. It collects controls (elements identified by an ngControl directive) and monitors their properties including their validity. It has its own valid property, which is true only if every contained control is valid.

In this example, we are pulling the "name" control out of its controls collection and assigning it to a template reference variable so that we can access the control's properties—such as the control's own valid property.

The Alter Ego is optional so we can leave that be.

Hero Power selection is required. We can add the same kind of error handling to the <select> if we want, but it's not imperative because the selection box already constrains the power to valid values.

Submit the form with ngSubmit

The user should be able to submit this form after filling it in. The Submit button at the bottom of the form does nothing on its own, but it will trigger a form submit because of its type (type="submit").

A "form submit" is meaningless at the moment. To make it meaningful, we'll update the <form> tag with another Angular directive, NgSubmit, and bind it to the HeroFormComponent.onSubmit() method:

lib/hero_form_component.html (excerpt)

<form (ngSubmit)="onSubmit()" #heroForm="ngForm">

We slipped in something extra there at the end! We defined a template reference variable, #heroForm, and initialized it with the value "ngForm".

The variable heroForm is now a handle to the NgForm as we discussed earlier with respect to ngControl, although this time we have a reference to the form rather than a control.

We'll bind the form's overall validity via the heroForm variable to the button's disabled property using an event binding. Here's the code:

lib/hero_form_component.html (excerpt)

<button type="submit" class="btn btn-default" [disabled]="!heroForm.form.valid">Submit</button>

If we run the application now, we find that the button is enabled. It doesn't do anything useful yet but it's alive.

Now if we delete the Name, we violate the "required" rule, which is duly noted in the error message. The Submit button is also disabled.

Not impressed? Think about it for a moment. What would we have to do to wire the button's enable/disabled state to the form's validity without Angular's help?

For us, it was as simple as:

  1. Define a template reference variable on the (enhanced) form element.
  2. Refer to that variable in a button many lines away.

Toggle two form regions (extra credit)

Submitting the form isn't terribly dramatic at the moment.

An unsurprising observation for a demo. To be honest, jazzing it up won't teach us anything new about forms. But this is an opportunity to exercise some of our newly won binding skills. If you aren't interested, go ahead and skip to this chapter's conclusion.

Let's do something more strikingly visual. Let's hide the data entry area and display something else.

Start by wrapping the form in a <div> and binding its hidden property to the HeroFormComponent.submitted property.

lib/hero_form_component.html (excerpt)

<div [hidden]="submitted"> <h1>Hero Form</h1> <form (ngSubmit)="onSubmit()" #heroForm="ngForm"> <!-- ... --> </form> </div>

The main form is visible from the start because the submitted property is false until we submit the form, as the following code from hero_form_component.dart shows:

bool submitted = false; onSubmit() { submitted = true; }

When we click the Submit button, the submitted flag becomes true and the form disappears as planned.

Now the app needs to show something else while the form is in the submitted state. Add the following block of HTML below the <div> wrapper we just wrote:

lib/hero_form_component.html (excerpt)

<div [hidden]="!submitted"> <h2>You submitted the following:</h2> <div class="row"> <div class="col-xs-3">Name</div> <div class="col-xs-9 pull-left">{{ model.name }}</div> </div> <div class="row"> <div class="col-xs-3">Alter Ego</div> <div class="col-xs-9 pull-left">{{ model.alterEgo }}</div> </div> <div class="row"> <div class="col-xs-3">Power</div> <div class="col-xs-9 pull-left">{{ model.power }}</div> </div> <br> <button class="btn btn-default" (click)="submitted=false">Edit</button> </div>

There's our hero again, displayed read-only with interpolation bindings. This slug of HTML appears only while the component is in the submitted state.

The HTML includes an Edit button whose click event is bound to an expression that clears the submitted flag.

When we click the Edit button, this block disappears and the editable form reappears.

That's as much drama as we can muster for now.

Conclusion

The Angular form discussed in this chapter takes advantage of the following framework features to provide support for data modification, validation, and more:

  • An Angular HTML form template.
  • A form component class with a Component decorator.
  • The ngSubmit directive for handling the form submission.
  • Template reference variables such as #heroForm, #name, #p, and #spy.
  • The ngModel directive for two-way data binding.
  • The ngControl directive for validation and form element change tracking.
  • The reference variable’s valid property on input controls to check if a control is valid and show/hide error messages.
  • Property binding to disable the submit button when the form is invalid.
  • Custom CSS classes that provide visual feedback to users about required invalid controls.

Here’s the code for the final version of the application:

import 'package:angular2/core.dart'; import 'hero.dart'; const List<String> _powers = const [ 'Really Smart', 'Super Flexible', 'Super Hot', 'Weather Changer' ]; @Component( selector: 'hero-form', templateUrl: 'hero_form_component.html') class HeroFormComponent { List<String> get powers => _powers; bool submitted = false; Hero model = new Hero(18, 'Dr IQ', _powers[0], 'Chuck Overstreet'); onSubmit() { submitted = true; } } <div class="container"> <div [hidden]="submitted"> <h1>Hero Form</h1> <form (ngSubmit)="onSubmit()" #heroForm="ngForm"> <div class="form-group"> <label for="name">Name</label> <input type="text" class="form-control" required [(ngModel)]="model.name" ngControl="name" #name="ngForm" > <div [hidden]="name.valid" class="alert alert-danger"> Name is required </div> </div> <div class="form-group"> <label for="alterEgo">Alter Ego</label> <input type="text" class="form-control" [(ngModel)]="model.alterEgo" ngControl="alterEgo" > </div> <div class="form-group"> <label for="power">Hero Power</label> <select class="form-control" required [(ngModel)]="model.power" ngControl="power" > <option *ngFor="let p of powers" [value]="p">{{p}}</option> </select> </div> <button type="submit" class="btn btn-default" [disabled]="!heroForm.form.valid">Submit</button> </form> </div> <div [hidden]="!submitted"> <h2>You submitted the following:</h2> <div class="row"> <div class="col-xs-3">Name</div> <div class="col-xs-9 pull-left">{{ model.name }}</div> </div> <div class="row"> <div class="col-xs-3">Alter Ego</div> <div class="col-xs-9 pull-left">{{ model.alterEgo }}</div> </div> <div class="row"> <div class="col-xs-3">Power</div> <div class="col-xs-9 pull-left">{{ model.power }}</div> </div> <br> <button class="btn btn-default" (click)="submitted=false">Edit</button> </div> </div> class Hero { int number; String name; String power; String alterEgo; Hero(this.number, this.name, this.power, [this.alterEgo]); String toString() => '$number: $name ($alterEgo). Super power: $power'; } name: hero_form description: Form example version: 0.0.1 environment: sdk: '>=1.19.0 <2.0.0' dependencies: angular2: ^2.2.0 dev_dependencies: browser: ^0.10.0 dart_to_js_script_rewriter: ^1.0.1 transformers: - angular2: platform_directives: - 'package:angular2/common.dart#COMMON_DIRECTIVES' platform_pipes: - 'package:angular2/common.dart#COMMON_PIPES' entry_points: web/main.dart - dart_to_js_script_rewriter <!DOCTYPE html> <html> <head> <title>Hero Form</title> <link rel="stylesheet" href="bootstrap.min.css"> <link rel="stylesheet" href="styles.css"> <link rel="stylesheet" href="forms.css"> <script defer src="main.dart" type="application/dart"></script> <script defer src="packages/browser/dart.js"></script> </head> <body> <hero-form>Loading...</hero-form> </body> </html> import 'package:angular2/platform/browser.dart'; import 'package:hero_form/hero_form_component.dart'; main() { bootstrap(HeroFormComponent); } .ng-valid[required] { border-left: 5px solid #42A948; /* green */ } .ng-invalid { border-left: 5px solid #a94442; /* red */ }