Form Validation in a NativeScript Angular Mobile Application

June 05, 2017 0 Comments

Form Validation in a NativeScript Angular Mobile Application

 

 

Not too long ago I wrote a tutorial to compliment the Angular documentation for form validation in an Angular web application. This was a follow-up to an old AngularJS tutorial I wrote for form validation in an Ionic Framework 1.0 application.

As you probably know, I'm a huge fan of NativeScript, and while NativeScript supports Angular, there are some minor differences between what you can do on the web versus what you can do in an Android and iOS mobile application. This is because of how NativeScript uses custom XML versus standard HTML markup.

In this article, we're going to explore how to validate form data client-side in a NativeScript with Angular mobile application.

You can download a zip of finished project here.

Creating a New NativeScript with Angular Project

The goal with this guide is to demonstrate the validation of components that allow for user input. Validation includes making sure fields have data, meet certain regular expression thresholds, or other things.

NativeScript Form Validation

Above is an example of what we're after.

The assumption at this point is that you have the NativeScript CLI installed and build platforms properly configured. Using the CLI, execute the following command:

tns create ns-validation-project --ng

The --ng flag above indicates that we are building an Angular project rather than a core project.

Our project is going to be far more basic than what the NativeScript Angular template provides us with at project creation. For this reason, we're going to do a bit of cleanup before continuing.

Within the project, open the app/app.routing.ts TypeScript file and clear our the routes array. This is going to be a single page application so it won't have any routes. The file should look like the following:

import { NgModule } from "@angular/core"; import { NativeScriptRouterModule } from "nativescript-angular/router"; import { Routes } from "@angular/router"; const routes: Routes = []; @NgModule({ imports: [NativeScriptRouterModule.forRoot(routes)], exports: [NativeScriptRouterModule] }) export class AppRoutingModule { }

The components and services that were created by the CLI also need to be removed from the project's app/app.module.ts file. In the end, the file should look like the following:

import { NgModule, NO_ERRORS_SCHEMA } from "@angular/core"; import { NativeScriptModule } from "nativescript-angular/nativescript.module"; import { NativeScriptFormsModule } from "nativescript-angular/forms"; import { AppRoutingModule } from "./app.routing"; import { AppComponent } from "./app.component"; @NgModule({ bootstrap: [ AppComponent ], imports: [ NativeScriptModule, AppRoutingModule, NativeScriptFormsModule ], declarations: [ AppComponent ], providers: [], schemas: [ NO_ERRORS_SCHEMA ] }) export class AppModule { }

At this point, the project should be good to go. While the cleanup wasn't absolutely necessary, it will help avoid any unnecessary confusion during the development process.

Using the Built-in Angular Validators

Angular, like AngularJS, has a set of included form validators. These validators work in web applications as well as NativeScript applications.

Take the example of an input field called email within the application. This field could have dirty, pristine, touched, valid, and invalid properties. A field is dirty when it has been used where as a field is pristine if it hasn't been used. This is different from touched which indicates the field has been blurred. If all rules for a field are valid, then the field is considered valid, otherwise it is invalid.

Open the project's app/app.component.html file and include the following HTML markup:

<ActionBar title="{N} Form Validation"></ActionBar> <StackLayout class="form"> <StackLayout class="input-field"> <Label text="Email" class="label font-weight-bold m-b-5"></Label> <TextField #email="ngModel" [(ngModel)]="input.email" class="input" required></TextField> <StackLayout class="hr-light"></StackLayout> </StackLayout> <StackLayout class="input-field"> <Label text="Password" class="label font-weight-bold m-b-5"></Label> <TextField #password="ngModel" secure="true" [(ngModel)]="input.password" class="input" required></TextField> <StackLayout class="hr-light"></StackLayout> </StackLayout> <StackLayout class="input-field"> <StackLayout *ngIf="email.errors && (email.dirty || email.touched)"> <Label *ngIf="email.errors.required" text="- An email is required -"></Label> </StackLayout> <StackLayout *ngIf="password.errors && (password.dirty || password.touched)"> <Label *ngIf="password.errors.required" text="- A password is required -"></Label> </StackLayout> </StackLayout> <StackLayout class="input-field"> <Button *ngIf="email.valid && password.valid" text="Login" class="btn btn-primary"></Button> </StackLayout> </StackLayout>

The above markup has two input fields and a button. The input fields both have a single validator that checks if the fields have data in them, hence, the required property. If the fields have been used or have been blurred, error text will appear and the button will remain hidden. If everything is good, no errors will show and the button will be available.

This is made possible through the template variable on each TextField. For example #email="ngModel" and #password="ngModel" give us this power. For these template variables to function, there must be a two-way binding using the [(ngModel)] attribute.

Up until now everything is very basic. In fact, you can consider this to compliment an article previously written by Nick Branstein on the subject of form validation.

Building a Custom Attribute Directive for Additional Validation

So what happens if the basic validation isn't enough for your mobile application? This is where custom attribute directives come into play. Custom directives could be useful if you wanted to set a minimum length, something already available on the web, for a TextField UI component. It isn't limited to lengths, for example, you could also validate emails or anything you can think up.

Within your project, create an app/input.directive.ts file which will contain all of our custom directives for input components. This file should look like the following in the end:

import { Directive, Input } from '@angular/core'; import { NG_VALIDATORS, Validator, AbstractControl } from '@angular/forms'; @Directive({ selector: '[minlength]', providers: [{provide: NG_VALIDATORS, useExisting: MinLengthDirective, multi: true}] }) export class MinLengthDirective implements Validator { @Input() minlength: string; public constructor() {} public validate(control: AbstractControl): {[key: string]: any} { return !control.value || control.value.length >= this.minlength ? null : { "minlength": true }; } } @Directive({ selector: '[email]', providers: [{provide: NG_VALIDATORS, useExisting: IsEmailDirective, multi: true}] }) export class IsEmailDirective implements Validator { public constructor() {} public validate(control: AbstractControl): {[key: string]: any} { let emailRegEx = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/i; let valid = emailRegEx.test(control.value); return control.value < 1 || valid ? null : {'email': true}; } }

There is a lot happening above, so let's break it down.

We have two directives, one of which you might recognize from my previous article on this same subject.

Starting with the MinLengthDirective, we have plans to force a minimum length, per what the user provides:

@Directive({ selector: '[minlength]', providers: [{provide: NG_VALIDATORS, useExisting: MinLengthDirective, multi: true}] }) export class MinLengthDirective implements Validator { @Input() minlength: string; public constructor() {} public validate(control: AbstractControl): {[key: string]: any} { return !control.value || control.value.length >= this.minlength ? null : { "minlength": true }; } }

The selector defines how we wish to use it within our UI component tags. By adding an @Input() of the same name, we can extract the value from the markup. Because this is form validation, we've implemented the validate method of the Validator interface. This method will be called with every change.

If a value exists for this form element and the length is greater than the defined length, it is considered true, at which point we want to return a null value. A null value indicates that our content is valid. If it is not valid, we return any kind of object and deal with it appropriately in the markup.

This brings us to IsEmailDirective, which contains regular expression validation:

@Directive({ selector: '[email]', providers: [{provide: NG_VALIDATORS, useExisting: IsEmailDirective, multi: true}] }) export class IsEmailDirective implements Validator { public constructor() {} public validate(control: AbstractControl): {[key: string]: any} { let emailRegEx = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/i; let valid = emailRegEx.test(control.value); return control.value < 1 || valid ? null : {'email': true}; } }

The same rules apply to the above. However, this time we are not taking input on the directive. As long as the directive appears, it will be used. In fairness, I didn't come up with the validation regular expression, but instead took it from Email RegEx. The expression checks to see if the input value is valid and returns appropriately.

Before we can use these directives, we need to add them to our @NgModule block. Open the project's app/app.module.ts file and import the directives as well as add them to the declarations array. The file should look like this in the end:

import { NgModule, NO_ERRORS_SCHEMA } from "@angular/core"; import { NativeScriptModule } from "nativescript-angular/nativescript.module"; import { NativeScriptFormsModule } from "nativescript-angular/forms"; import { AppRoutingModule } from "./app.routing"; import { AppComponent } from "./app.component"; import { MinLengthDirective, IsEmailDirective } from "./input.directive"; @NgModule({ bootstrap: [ AppComponent ], imports: [ NativeScriptModule, AppRoutingModule, NativeScriptFormsModule ], declarations: [ AppComponent, MinLengthDirective, IsEmailDirective ], providers: [], schemas: [ NO_ERRORS_SCHEMA ] }) export class AppModule { }

With the @NgModule block ready to go, we can start using the directives in the markup. Revisiting, the app/app.component.html file, check out the following markup:

<ActionBar title="{N} Form Validation"></ActionBar> <StackLayout class="form"> <StackLayout class="input-field"> <Label text="Email" class="label font-weight-bold m-b-5"></Label> <TextField #email="ngModel" [(ngModel)]="input.email" class="input" required email></TextField> <StackLayout class="hr-light"></StackLayout> </StackLayout> <StackLayout class="input-field"> <Label text="Password" class="label font-weight-bold m-b-5"></Label> <TextField #password="ngModel" secure="true" minlength="5" [(ngModel)]="input.password" class="input" required></TextField> <StackLayout class="hr-light"></StackLayout> </StackLayout> <StackLayout class="input-field"> <StackLayout *ngIf="email.errors && (email.dirty || email.touched)"> <Label *ngIf="email.errors.required" text="- An email is required -"></Label> <Label *ngIf="email.errors.email" text="- Email must be valid -"></Label> </StackLayout> <StackLayout *ngIf="password.errors && (password.dirty || password.touched)"> <Label *ngIf="password.errors.required" text="- A password is required -"></Label> <Label *ngIf="password.errors.minlength" text="- Password must be longer than 5 characters"></Label> </StackLayout> </StackLayout> <StackLayout class="input-field"> <Button *ngIf="email.valid && password.valid" text="Login" class="btn btn-primary"></Button> </StackLayout> </StackLayout>

Let's look at what changed in the above.

In the TextField that represents the email address, we've added an email attribute. This matches the directive selector that we had defined. As you can see it has no value because it doesn't need one. By adding it, we are saying we want to validate only the field data. In the errors block, we are also checking for email related validation errors.

In the TextField that represents the password, we've added a minlength attribute that contains a numeric value. Remember, the value is what we're checking inside the directive alongside the field value. We are also adding error printing for this validator.

Conclusion

You just saw how to add form validation to a NativeScript Angular mobile application. This form validation included what was already available in Angular as well as custom validation through Angular directives. By validating forms, you give your user experience some extra flair. It should not be a replacement to server-side validation with backend code.


Tag cloud