Search code examples
angularvalidationangular-reactive-forms

Allow input number only, else show message


Allowing the user to input number only is working.

But if the user tries to enter something other than number it must show message.

I have tried Validators with pattern, but I can't make the message show.

How to show error message when user doesn't enter a number?

Reference:https://stackblitz.com/edit/angular-input-field-to-accept-only-numbers-vewaxf?file=src/app/only-number.directive.ts

.html file

<h3>Template Driven Form</h3>
<form #form="ngForm">
  <input name="number" type="text" onlyNumber [(ngModel)]="number" />
  <p>model value: {{ number }}</p>
  <p>form dirty: {{ form.dirty }}</p>
</form>

<h3>Reactive Form</h3>
<form [formGroup]="formGroup">
  <input formControlName="number" type="text" onlyNumber />
  <p>model value: {{ formGroup.get('number').value }}</p>
  <p>form dirty: {{ formGroup.dirty }}</p>
  <!-- <div *ngIf="methoddd()">Sorry</div>
</form> -->
</form>

.ts file

import { Directive, ElementRef, forwardRef, HostListener, Renderer2 } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';

@Directive({
    selector: '[onlyNumber]',
    providers: [{
        provide: NG_VALUE_ACCESSOR,
        useExisting: forwardRef(() => OnlyNumberDirective),
        multi: true
    }]
})
export class OnlyNumberDirective implements ControlValueAccessor {
    private onChange: (val: string) => void;
    private onTouched: () => void;
    private value: string;

    constructor(
        private elementRef: ElementRef,
        private renderer: Renderer2
    ) {
    }

    @HostListener('input', ['$event.target.value'])
    onInputChange(value: string) {
        const filteredValue: string = filterValue(value);
        this.updateTextInput(filteredValue, this.value !== filteredValue);
    }

    @HostListener('blur')
    onBlur() {
        this.onTouched();
    }

    private updateTextInput(value: string, propagateChange: boolean) {
        this.renderer.setProperty(this.elementRef.nativeElement, 'value', value);
        if (propagateChange) {
            this.onChange(value);
        }
        this.value = value;
    }

    // ControlValueAccessor Interface
    registerOnChange(fn: any): void {
        this.onChange = fn;
    }

    registerOnTouched(fn: any): void {
        this.onTouched = fn;
    }

    setDisabledState(isDisabled: boolean): void {
        this.renderer.setProperty(this.elementRef.nativeElement, 'disabled', isDisabled);
    }

    writeValue(value: any): void {
        value = value ? String(value) : '';
        this.updateTextInput(value, false);
    }
}

function filterValue(value): string {
    return value.replace(/[^0-9]*/g, '');
}

Solution

  • In your directive, you need to get references to form control via ngModel and ngControl, and then you need to set errors manually, and also you need to set previous value to form control to prevent it from changing.

    In general, this is a job for custom validators, not a directive.

    Try this:

    <h3>Template Driven Form</h3>
    <form #form="ngForm">
      <input name="number" type="text" onlyNumber [(ngModel)]="number" />
      <div *ngIf="!form.valid">{{form.controls.number.errors| json}}</div>
      <p>form dirty: {{ form.dirty }}</p>
    </form>
    
    <h3>Reactive Form</h3>
    <form [formGroup]="formGroup">
      <input formControlName="number" type="text" onlyNumber />
      <p>model err: {{ formGroup.get('number').errors | json }}</p>  
      <p>model value: {{ formGroup.get('number').value }}</p>
      <p>form dirty: {{ formGroup.dirty }}</p>
     
    </form>
    
    import { Directive, ElementRef, HostListener} from '@angular/core';
    import {  NgControl, NgModel } from '@angular/forms';
    
    @Directive({
        selector: '[onlyNumber]',
        providers: [NgModel]
    })
    export class OnlyNumberDirective  {
        constructor(private el: ElementRef,private control : NgControl, private ngModel: NgModel) {
        }  
        @HostListener('input', ['$event']) onChange($event: any) {
    
            const initialValue = this.el.nativeElement.value;
        
            const newValue = initialValue.replace(/[^0-9]*/g, '');
    
            if ( initialValue !== newValue) {
    
                $event.stopPropagation();
                this.control.control.setValue(newValue)
                this.control.control.setErrors({'err': 'only numbers allowed'});
                this.ngModel.control.setErrors({'err': 'only numbers allowed'});
            }
          }
    }
    

    Stackblitz