Wednesday 29 October 2014

Use input[type="email"]

The email input type was one of the most exciting new input types that HTML5 brought with it for me. Email validation is a pain. I want to be able to leave complex regex patterns to the back end devs, that's the kind of thing they're better at (and probably enjoy more) than I do. AngularJs does a fantastic job of building on top of the built in functionality for this input type. If you really want to play around with those crazy regex strings then you can thanks to an attribute that takes a regex string for parsing too.

HTML to Write

<input type="email"
name="email"
ng-model="emailValue"
ng-required="checkboxValue"
ng-minlength="5"
ng-maxlength="255"
ng-pattern=""
ng-change="emailChanged()"/>


AngularJs uses the type attribute to trigger the specific directive. The name attribute is optional, but if you want to be able to programatically query anything about the input you need to have it. There are two ways to query if the input is in a valid state using JavaScript, and it's easier to use both of them if there's a name attribute to reference it by.

ngModel declares the $scope variable that the value of the input should be assigned to. I've tried assigning a default value to this variable in the controller before, but think that this gives quite an annoying user experience as they have to delete it before they can enter their own. There are times when you may be able to make an accurate guess at what their email address is, or you are about to create the email address that is entered into this field, or some other reason that would make sense to populate this value to save the user some effort. I would recommend that you don't assign a default value to this variable though, and if you do take a very long look at why it is you are.

ngRequired creates the standard HTML5 required attribute if the value of the attribute is true. This allows you to make one field's requirement dependent upon a given boolean, such as could be generated by another field on the same form. For instance, you may be offering your user to opt into a newsletter and only want to take their email address if they've selected that they want to receive it. In my example above I've used the $scope variable of checkboxValue (as seen in my post about input[type="checkbox"]). By doing this my email input is required only when the user has checked that particular checkbox. If you don't want to set this programatically, then just use required.

ngMinLength and ngMaxLength do what they say on the tin. They allow you to set your own validators based on the length of the user's input. I can't think of a use case for these, so please let me know if you can. I've put five as the minimum above as that's the shortest length an email can possibly be. I've put 255 in as the maximum because that's a fairly typical binary maximum. If you have these attributes on your element then the value of them must be populated with some numeric value otherwise the value of your input will never be valid. Do not leave them empty.

ngPattern is where you can insert your own regex string. This is good if you want to restrict the kind of email address that your users can use. For instance, you may only want your users to be able to sign up or log in if they're part of a certain organisation and therefore need to prove it by providing an email address on that organisation's domain. If it's empty (as mine is) then nothing is rendered in the browser, as you can see below.

Finally, ngChange is an expression to execute when the user changes the value of the input. In the example above I've put a function that is declared on the $scope. This will only trigger if the value of the input is currently valid, or the change that the user has made makes the value of the input valid. I find it very confusing when debugging that this isn't hit every time the user makes some change to the value, but it does mean that you can guarantee that this expression or function will only be run if the value is valid and therefore you don't have to check that it is.

Gotcha

One thing worth noting is that an empty string will validate. This seems like a bug to me, but that's how it works.

Generated HTML

<input type="email"
name="email"
ng-model="emailValue"
ng-required="checkboxValue"
ng-minlength="5"
ng-maxlength="255"
ng-change="emailChanged()"
class="ng-pristine ng-untouched ng-valid ng-valid-email ng-valid-pattern ng-valid-minlength ng-valid-maxlength ng-valid-required">


This is the HTML that is generated in the browser once the page loads. As you can see most of it is as written. The classes are where I think this starts to get interesting.

CSS Classes

If you've read other posts of mine on input types, you're going to find some of this repetitive...

AngularJs assigns different classes to the input depending on the state of the input and how the user has interacted with it. It pretty much has all the bases covered, which is great for us interface developers. Thanks to these automatically assigned classes we can concentrate on providing an intuitive user experience without having to worry about building all the code to check and handle the user's interaction with it. I know I could have produced many better forms if I'd had AngularJs to work with at the time thanks to these shortcuts, but when there's a deadline and budget constraints it's the nice-to-haves like this that get cut. These classes come from ngModel.NgModelController.

ngPristine means that the user hasn't yet interacted with the input.

ngUntouched means the input hasn't lost focus yet. This could be a bit of a gotcha as it could mean that the input hasn't had focus to lose, or that the input has focus currently and for the first time and therefore hasn't lost it yet.

ngValid means that the state of the input is valid. This includes all the validators that you have added on as attributes. As mentioned above, watch out as an empty string is a valid input value.

ngValid* shouldn't be too hard to work out. These are flags for each of the validators that are valid.

When the user has tabbed over the input in the form without interacting with the input in any other way AngularJs will remove ngUntouched and add ngTouched (ng-touched). ngTouched means that the input has had focus at some point and has now lost it.

Once the user starts entering a value into the input the ngValid and ngValid* classes will be added and removed depending upon whether or not any given validator (or all the validators in the case of ngValid) are returning that the input value is valid. When these conditions are not true AngularJs will show ngInvalid and ngInvalid*:

ng-invalid-email ng-invalid-pattern ng-invalid-minlength ng-invalid-maxlength ng-invalid-required

The above is for illustrative purposes only. I've as yet been unable to trigger ngInvalidRequired on an email input type, and of course you're not going to have an invalid min and max length together unless you've made an error populating the validators.

At this point AngularJs will also remove ngPristine and replace it with ngDirty. ngDirty signifies that the user has made some interaction with the value of this input by changing it in some way.

Querying Validity

These classes are fantastic as styling hooks so that you can inform the user that their input is or isn't valid, but what if you want to know this programatically so that you can do some other logic based on that? Thankfully HTML5 has thought about this, and AngularJs has added some sugar on top for use in the DOM. This is where the name attributes come in. Assume that the above input is wrapped in the following form element:

<form ng-submit="submit()" name="angularForm"></form>

From the Controller

In order to query the validity of the input value from the controller you need to use vanilla HTML5:

angularForm.email.validity.valid

This will return true if valid and false if invalid, which makes it perfect for use in an if statement. Don't use this to set a $scope variable to be used on the DOM though.

From the DOM

To query the validity of the input value from the DOM use AngularJs's syntactic sugar:

angularForm.email.$valid

This also returns true when valid and false when invalid, which makes it perfect for showing or hiding error messages related to this input.

Note: Normally I would prefix all the ng-* attributes with data- in order to make the HTML valid. I omitted this time for the sake of clarity.

No comments:

Post a Comment