Forms Validation with NumericTextBox

Perform Custom Validation Using FormValidator in Angular NumericTextBox Component

This help topic describes how to use Custom Validators in Angular Template Driven Form to validate numeric values. The numeric values in our simple application will be entered in a Smart.NumericTextBox Angular component which will be dynamically validated using an Angular directive.

The goals to be achieved are:

  • Build an Angular form with a component and template.
  • Use ngModel to create two-way data bindings for reading and writing input-control values.
  • Validate user input and show validation errors to users and enable/disable form controls.
  • Use template refeence variables.

Once implemented, the form will look as follows:

Angular numeric value form validation

Follow the steps below to implement this sample form:

  1. ng new my-project
  2. cd my-project.
  3. ng add smart-webcomponents-angular.
  4. ng-serve and in your browser enter localhost:4200.
  5. Navigate to the \src\app folder.
  6. Create a new file named numeric.directive.ts containing the following code:
    import { Directive } from '@angular/core';
    import { AbstractControl, NG_VALIDATORS, ValidationErrors, Validator, ValidatorFn } from "@angular/forms";
    
    function validateNumericValue(): ValidatorFn {
        return (control: AbstractControl) => {
            let isValid = control.value > 10 && control.value <= 100;
    
            if (isValid) {
                return null;
            }
            else {
                return { 'numericValueCheck': 'failed' };
            }
        }
    }
    
    @Directive({
        selector: '[appCheckNumericValue]',
        providers: [{ provide: NG_VALIDATORS, useExisting: CheckNumericValueDirective, multi: true }]
    })
    export class CheckNumericValueDirective implements Validator {
        private valFn: ValidatorFn;
    
        constructor() {
            this.valFn = validateNumericValue();
        }
    
        validate(c: AbstractControl): ValidationErrors | null {
            return this.valFn(c);
        }
    }

    The validator checks the value of a NumericTextBox control - if it is less than or equal to 10 or larger than 100, it returns a custom validation error named numericValueCheck.

  7. Open app.module.ts and put the following content:
    import { NgModule } from '@angular/core';
    import { BrowserModule } from '@angular/platform-browser';
    import { FormsModule } from '@angular/forms';
    
    import { ButtonModule } from 'smart-webcomponents-angular/button';
    import { InputModule } from 'smart-webcomponents-angular/input';
    import { NumericTextBoxModule } from 'smart-webcomponents-angular/numerictextbox';
    import { CheckNumericValueDirective } from './numeric.directive';
    
    import { AppComponent } from './app.component';
    
    @NgModule({
        declarations: [AppComponent, CheckNumericValueDirective],
        imports: [BrowserModule, FormsModule, ButtonModule, InputModule, NumericTextBoxModule],
        bootstrap: [AppComponent],
        entryComponents: [AppComponent]
    })
    
    export class AppModule { }

    The directive CheckNumericValueDirective is defined in declarations and the modules of the Smart components we will use - in imports.

  8. Open app.component.ts and put the following content:
    import { Component, ViewChild, OnInit, AfterViewInit, OnChanges } from '@angular/core';
    import { Directive, Input } from "@angular/core";
    import {
    	Validator,
    	ValidationErrors,
    	FormGroup,
    	NG_VALIDATORS,
    	NgForm,
    	Validators
    } from "@angular/forms";
    
    
    import { DropDownListComponent } from 'smart-webcomponents-angular/dropdownlist';
    
    export class Product {
    	constructor(
    		public productName: string,
    		public shippingAddress: string,
    		public quantity: string | number
    	) { }
    }
    
    
    @Component({
    	selector: 'app-root',
    	templateUrl: './app.component.html',
    	styleUrls: ['./app.component.css']
    })
    
    export class AppComponent implements AfterViewInit, OnInit {
    	productType = ['Bananas', 'Oranges', 'Coffee', 'Sugar', 'Cocoa', 'Spices', 'Silk'];
    	model = new Product(this.productType[2], '552  Whitetail Lane, Dallas, TX', null);
    	submitted = false;
    
    	onSubmit() { this.submitted = true; }
    
    	ngOnInit(): void {
    		// onInit code.
    	}
    
    	ngAfterViewInit(): void {
    		// afterViewInit code.
    		this.init();
    	}
    
    	init(): void {
    		// init code.
    	}
    
    	newShipment() {
    		this.model = new Product('', '', null);
    	}
    
    	get diagnostic() { return JSON.stringify(this.model); }
    }

    Here we define our application model as an instance of the class Shipment with the properties productName, shippingAddress, and quantity, all of which will be filled in form components.

  9. Open app.component.html and put the following content:
    <div [hidden]="submitted" class="container">
        <h1>Bulk Product Shipping</h1>
        <form (ngSubmit)="onSubmit()" #productForm="ngForm">
            <div class="form-row">
                <label for="productName">Product Name</label>
                <smart-input required class="form-component" [(ngModel)]="model.productName" name="productName"
                    id="productName" #productName="ngModel" [dataSource]="productType" [dropDownButtonPosition]="'right'"
                    [readonly]="true">
                </smart-input>
                <div [hidden]="productName.valid || productName.pristine" class="alert alert-danger">
                    Name is required
                </div>
            </div>
    
            <div class="form-row">
                <label for="shippingAddress">Shipping Address</label>
                <smart-input class="form-component" [(ngModel)]="model.shippingAddress" name="shippingAddress"
                    id="shippingAddress"></smart-input>
            </div>
    
            <div class="form-row">
                <label for="numerictextboxQuantity">Quantity</label>
                <smart-numeric-text-box id="numerictextboxQuantity" name="numerictextboxQuantity" class="form-component"
                    #numerictextboxQuantity="ngModel" [(ngModel)]="model.quantity" required appCheckNumericValue
                    [nullable]="true" [placeholder]="'Specify'" [showUnit]="true" [unit]="'kg'"></smart-numeric-text-box>
            </div>
    
            <div *ngIf="numerictextboxQuantity.errors?.numericValueCheck && (numerictextboxQuantity.dirty || numerictextboxQuantity.touched)"
                class="alert alert-danger">
                Please choose a quantity between 11 and 100 kg.
            </div>
    
            <div class="smart-stack-layout">
                <smart-button [disabled]="!productForm.form.valid" type="submit" class="success item">Submit</smart-button>
                <smart-button type="submit" (click)="newShipment(); productForm.reset()" class="primary item">New Shipment
                </smart-button>
            </div>
        </form>
    </div>
    
    <div [hidden]="!submitted">
        <h2>You submitted the following:</h2>
        <div class="row">
            <div class="col-xs-3">Product Name</div>
            <div class="col-xs-9">{{ model.productName }}</div>
        </div>
        <div class="row">
            <div class="col-xs-3">Shipping Address</div>
            <div class="col-xs-9">{{ model.shippingAddress }}</div>
        </div>
        <div class="row">
            <div class="col-xs-3">Quantity</div>
            <div class="col-xs-9">{{ model.quantity }}</div>
        </div>
        <br>
        <smart-button class="primary" (click)="submitted=false">Edit</smart-button>
    </div>
    <br /><br />
    <h2>Changes Log:</h2>
    {{diagnostic}}

    A validation error message is shown to the user if the numeric value in the Smart.NumericTextBox does not pass the validation executed by the appCheckNumericValue directive.

ngModel

The Two-way data binding is achieved with ngModel. Note the [(ngModel)]="model.productName", [(ngModel)]="model.shippingAddress", [(ngModel)]="model.quantity" in the template above (app.component.html).
You also need a template reference variable to access the NumericTextBox's Angular control from within the template:
<smart-numeric-text-box id="numerictextboxQuantity" name="numerictextboxQuantity" class="form-component"
    #numerictextboxQuantity="ngModel" [(ngModel)]="model.quantity" required appCheckNumericValue
    [nullable]="true" [placeholder]="'Specify'" [showUnit]="true" [unit]="'kg'"></smart-numeric-text-box>
Here you created a variable called numerictextboxQuantity and gave it the value "ngModel".


Example