Validating an Input

The FormControl constructor has three optional parameters. The first is a default value for the form element. The type of this parameter is also used to inform Angular about what sort of form element to expect to be attached to this FormControl. The second and third parameters are arrays that contain the synchronous and asynchronous validation logic, respectively, for this individual element. Each validation rule is a function that is given the form control element (this is where we use the AbstractControl that was imported earlier) and then returns either null, if there’s no error, or an object containing details about what’s gone wrong. Let’s take a look at how we might implement a validator for the phone number.

Built-In Validators

images/aside-icons/note.png In this section, you’ll build your own validator, but it’s important to remember that Angular comes with a handful of pre-written validators. You’ll learn about those in the next section.

It’s possible to ignore all three parameters, and Angular will default to a text input with no validation. We want some validation to ensure that the user gives us a valid phone number. In this case, all of the validation can be done synchronously—there’s no need to ask a server for extra validation help, so the third parameter is unnecessary. We’ll have a single, synchronous validation rule that ensures the user has entered a ten-digit number.

 export​ ​class​ PhoneNumComponent ​implements​ OnInit {
  phoneNumber = ​new​ FormControl(​''​, [
  (control: AbstractControl) => {
 // remove anything that isn't a digit
 const​ numDigits = control.value.replace(​/​​[^d]​​+/g​, ​''​).length;
 // Only worried about US-based numbers for now, no need for country code
 if​ (numDigits === 10) { ​return​ ​null​; }
 // Uh oh, something's wrong
 if​ (numDigits > 10) {
 return​ {
  tooLong: { numDigits }
  };
  } ​else​ {
 return​ {
  tooShort: { numDigits }
  };
  }
  }
  ]);

When the phone number is valid, the validator function returns null to indicate that there’s no error. When there is an error, a validator function returns an object. The keys of the object will be put on the keys of the error property of the formControl. If phoneNum.errors is falsy, you know the input is valid. Otherwise, there’s an object with a key for each error. The values on the error object can be any information that would aid in debugging, or just true if there’s nothing more to be said.

In this case, we include the current length, so the user knows whether they need to add more digits or they pressed a key one too many times. The convention is to attach relevant information to the validator, but have the full error message on the page to make it easier to translate into other languages. Now that we’re validating our phone number object, we need to update the view to display this new information.

 <input ​[​formControl​]="​phoneNumber​"​ />
 <div ​*​ngIf=​"phoneNumber.invalid"​>
  <div ​*​ngIf=​"(phoneNumber.dirty || phoneNumber.touched)"​>
  <div ​*​ngIf=​"phoneNumber.errors.tooLong"​>
  There's too many digits in your phone number!
  You entered {{ phoneNumber.errors.tooLong.numDigits }}
  digits (required: 10)
  </div>
  <div ​*​ngIf=​"phoneNumber.errors.tooShort"​>
  Your phone number is too short!
  You entered {{ phoneNumber.errors.tooShort.numDigits }}
  digits (required: 10)
  </div>
  </div>
 </div>

You’ll notice that these errors are gated off by an *ngIf statement. Angular throws property access errors if errors is undefined (that is, the input is correct), and we try to access the tooLong property (as opposed to AngularJS, which silently swallowed those errors). To avoid throwing errors when the element is correct, we check the invalid property, which will be true if there are any errors in validating phoneNumber.

The two if statements have been separated for clarity. The second if statement, (phoneNumber.dirty || phoneNumber.touched) is a little more complicated. No one likes validation errors before they’ve even started, so this snippet only displays errors after the phoneNumber input has been selected or changed. The dirty state indicates that the input has changed from the original value—the user has input some value. The touched state is true if the user has focused on the form element. We need to check both, because sometimes automatic form fillers change an input without triggering the touched state (the input is dirty but not touched), or the user might highlight the input without changing anything (the input is touched but not dirty). You can listen in to these changes through phoneNumber.valueChanges, an observable of value changes.

Once the outermost *ngIf is satisfied, it’s time to tell the user what went wrong. Rather than a vague message of, “There were errors in submitting your form,” at the end of the form, the user now has immediate, inline feedback as to what they’ve done wrong.

Finally, Angular will add classes to an input according to the state of that input. These classes are:[6]

State

If True

If False

The control has been visited.

ng-touched

ng-untouched

The control’s value has changed.

ng-dirty

ng-pristine

The control’s value is valid.

ng-valid

ng-invalid

You can add styling details to these CSS classes to add visual cues to help the user figure out what’s valid and what’s not. In the general validation case, we want to alert the user when they’ve changed an input (.ng-dirty) and it’s not valid (.ng-invalid). Add these styles to styles.css:

 .ng-invalid.ng-dirty {
  border-color: #a94442;
  -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075);
  box-shadow: inset 0 1px 1px rgba(0,0,0,.075);
 }

We were able to build out all of this with minimal code, and the criteria for a valid input is clear to both the engineer looking at the code and the user filling out the form. Bravo! Now, let’s build out a full registration form.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.129.218.69