Guide on showing error message according to personalized validation regulations in Angular 2?

Utilizing a template-driven strategy for constructing forms in Angular 2, I have successfully implemented custom validators that can be utilized within the template.

However, I am facing an issue with displaying specific error messages associated with distinct errors. I aim to identify the reason behind the form's invalidity. How can this be accomplished?


import { Component } from '@angular/core';

import { NgForm } from '@angular/forms';

import { Site } from './../site';

import { BackendService } from './../backend.service';

import { email } from './../validators';

import { CustomValidators } from './../validators';

@Component({
    templateUrl: 'app/templates/form.component.html',
    styleUrls: ['app/css/form.css'],
    directives: [CustomValidators.Email, CustomValidators.Url, CustomValidators.Goof],
    providers: [BackendService]
})

export class FormComponent {
    active = true;
    submitted = false;
    model = new Site();

    onSubmit() {
        this.submitted = true;
        console.log(this.model);
    }

    resetForm() {
        this.model = new Site();
        this.submitted = false;
        this.active = false;
        setTimeout(() => this.active = true, 0);
    }

    get diagnostics() {
        return JSON.stringify(this.model)
    }
}

import { Directive, forwardRef } from '@angular/core';
import { NG_VALIDATORS, FormControl } from '@angular/forms';
import { BackendService } from './backend.service';

function validateEmailFactory(backend:BackendService) {
    return (c:FormControl) => {
        let EMAIL_REGEXP = /^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i;

        return EMAIL_REGEXP.test(c.value) ? null : {
            validateEmail: {
                valid: false
            }
        };
    };
}

export module CustomValidators {

    @Directive({
        selector: '[email][ngModel],[email][formControl]',
        providers: [
            {provide: NG_VALIDATORS, useExisting: forwardRef(() => CustomValidators.Email), multi: true}
        ]
    })
    export class Email {
        validator:Function;

        constructor(backend:BackendService) {
            this.validator = validateEmailFactory(backend);
        }

        validate(c:FormControl) {
            return this.validator(c);
        }
    }

    @Directive({
        selector: '[url][ngModel],[url][formControl]',
        providers: [
            {provide: NG_VALIDATORS, useExisting: forwardRef(() => CustomValidators.Url), multi: true}
        ]
    })
    export class Url {
        validator:Function;

        constructor(backend:BackendService) {
            this.validator = validateEmailFactory(backend);
        }

        validate(c:FormControl) {
            var pattern = /(https?:\/\/)?(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,4}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)/;

            return pattern.test(c.value) ? null : {
                validateEmail: {
                    valid: false
                }
            };
        }
    }

    @Directive({
        selector: '[goof][ngModel],[goof][formControl]',
        providers: [
            {provide: NG_VALIDATORS, useExisting: forwardRef(() => CustomValidators.Goof), multi: true}
        ]
    })
    export class Goof {
        validate(c:FormControl) {
            return {
                validateGoof: {
                    valid: false
                }
            };
        }
    }
}

Answer №1

To determine if a control has a specific error, you can utilize the AbstractControl#hasError(...) method. Both FormGroup and FormControl are considered as AbstractControls. For a FormControl, you simply need to pass the error name as an argument. Here's an example using a custom regex validator for a control:

function regexValidator(control: FormControl): {[key:string]: boolean} {
  if (!control.value.match(/^pee/)) {
    return { 'badName': true };
  }
}

<div *ngIf="!nameCtrl.valid && nameCtrl.hasError('badName')"
     class="error">Name must start with <tt>pee</tt>.
</div>

The validator function should return a map with the error name as the key. This key is then used to check for errors in the hasError method.

For a FormGroup, you can provide an additional parameter which specifies the path to the particular FormControl.

<div *ngIf="!form.valid && form.hasError('required', ['name'])"
     class="error">Form name is required.</div>

In this case, `name` serves as the identifier for the input FormControl.

Below is an updated example that showcases validation errors for both FormControl and FormGroup:


UPDATE

I managed to get it working but it turned out a bit verbose. I couldn't directly access individual FormControl instances for inputs. Instead, I created a reference to the FormGroup:

<form #f="ngForm" novalidate>

To check for validity, I utilized the hasError method overload by passing the path of the form control name. For <input> elements that use name and ngModel, the value of name gets added as the name of the FormControl to the main

FormGroup</code. Thus, it can be accessed as follows:</p>

<pre><code>`f.form.hasError('require', ['nameCtrl'])`

Assuming `name=nameCtrl`. Note the inclusion of `f.form`. The `f` represents the `NgForm` instance with a member variable `form` representing the `FormGroup`.

Below is the refactored template example:

import { Component, Directive } from '@angular/core';
import {
  FormControl,
  Validators,
  AbstractControl,
  NG_VALIDATORS,
  REACTIVE_FORM_DIRECTIVES
} from '@angular/forms';

function validateRegex(control: FormControl): {[key:string]: boolean} {
  if (!control.value || !control.value.match(/^pee/)) {
    return { 'badName': true };
  }
}
@Directive({
  selector: '[validateRegex]',
  providers: [
    { provide: NG_VALIDATORS, useValue: validateRegex, multi: true }
  ]
})
export class RegexValidator {
}

@Component({
  moduleId: module.id,
  selector: 'validation-errors-template-demo',
  template: `
    <div>
      <h2>Differentiate Validation Errors</h2>
      <h4>Type in "peeskillet"</h4>
      <form #f="ngForm" novalidate>
        <label for="name">Name: </label>

        <input type="text" name="nameCtrl" ngModel validateRegex required />

        <div *ngIf="!f.form.valid && f.form.hasError('badName', ['nameCtrl'])"
             class="error">Name must start with <tt>pee</tt>.</div>

        <div *ngIf="!f.form.valid && f.form.hasError('required', ['nameCtrl'])"
             class="error">Name is required.</div>
      </form>
    </div>
  `,
  styles: [`
    .error {
      border-radius: 3px;
      border: 1px solid #AB4F5B;
      color: #AB4F5B;
      background-color: #F7CBD1;
      margin: 5px;
      padding: 10px;
    }
  `],
  directives: [REACTIVE_FORM_DIRECTIVES, RegexValidator]
})
export class ValidationErrorsTemplateDemoComponent {

}

Answer №2

I developed a new set of directives inspired by ng-messages in AngularJs to address this issue in Angular. Check it out at: https://github.com/DmitryEfimenko/ngx-messages

<div [val-messages]="myForm.get('email')">
  <span val-message="required">Kindly provide your email address</span>
  <span val-message="server" useErrorValue="true"></span>
</div>

Answer №3

To implement template-driven validation, you can create an attribute directive like the one shown below:

import { Model } from '../../models';
import { Directive, Attribute, forwardRef, Input } from '@angular/core';
import { NG_VALIDATORS, Validator, AbstractControl } from '@angular/forms';

@Directive({
    selector: '[unique-in-list][formControlName],[unique-in-list][formControl],[unique-in-list][ngModel]',
    providers: [
        { provide: NG_VALIDATORS, useExisting: forwardRef(() => UniqueInListDirectiveValidator), multi: true }
    ]
})
export class UniqueInListDirectiveValidator implements Validator {
     @Input('list') private list: Model[];
     @Input('property') private _property: string;
     @Input('thisid') private _myId: string;

     validate(control: AbstractControl): { [key: string]: any } {

        let currValue = control.value;
        if (currValue && currValue.length > 0 && this.list && Array.isArray(this.list) && this.list.length > 0) {
            let model = this.list.find(
                (testModel: Model) => {
                    return testModel.modelId !== this._myId && testModel[this._property] === currValue;
                }
            );
            if (model) {
                return { isUnique: true }
            }
        }
        return null;
    }
}

You can then use the following markup in your template:

<input
    type="text"
    #nameVar="ngModel"
    name="name"
    class="form-control name-field"
    unique-in-list
    [list]="models"
    [thisid]="model.modelId"
    property="name"
    [(ngModel)]="model.name"
    required
/>
<div *ngIf="nameVar.errors?.required && (submitted || !nameVar.pristine)" class="alert-classes-here">
    <div>Name is required</div>
</div>
<div *ngIf="nameVar.errors?.isUnique && (submitted || !nameVar.pristine)" class="alert-classes-here">
    <div>Name is already taken</div>
</div>

Hope this helps!

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

The problem with the "typescript-react-apollo" codegen plugin is that it is declaring block-scope variables more than once in the generated

Recently, I embarked on a new project utilizing NextJS with graphql-codegen to auto-generate my apollo-react types from the API. Unfortunately, it seems to be generating duplicate exported variables right from the start, causing a typescript error and hi ...

An issue was encountered at node_modules/@fullcalendar/core/main.d.ts(1196,54), error TS1144: Expecting either '{' or ';'

When attempting to execute npm run build in my project, I encountered the following error: ERROR in node_modules/@fullcalendar/core/main.d.ts(1196,54): error TS1144: '{' or ';' expected. node_modules/@fullcalendar/core/main.d.ts(1197,34 ...

React: Issue accessing URL parameters using useParams() within a nested component

In my demo application, there are two components - QuoteDetail and Comments. Both require URL parameters, but I am only able to access them in the parent component. App.tsx: <Switch> // ... <Route path="/quotes" exact> <Al ...

Guide to creating a Map with typescript

I've noticed that many people are converting data to arrays using methods that don't seem possible for me. I'm working with React and TypeScript and I have a simple map that I want to render as a list of buttons. Here is my current progres ...

The Next.js website displays a favicon in Chrome, but it does not appear in Brave browser

As I work on my debut next.js website, I am configuring the favicon in index.js like this: <Head> <title>Create Next App</title> <link rel="icon" href="/favicon.ico" /> </Head> Initially, all my source ...

Exploring Ways to Navigate to a Component Two Steps Back in Angular

Let's say I have three routes A->B->C. I travel from A to B and then from B to C. Now, is it possible for me to go directly from C to A? ...

Is it possible to retrieve a value obtained through Request.Form?

Within my Frontend, I am passing an Object with a PersonId and a FormData object. const formData = new FormData(); for (let file of files){ formData.append(file.name, file,); } formData.append('currentId',this.UserId.toString()); const upl ...

Unclear error message when implementing union types in TypeScript

Currently, I am attempting to define a union type for a value in Firestore: interface StringValue { stringValue: string; } interface BooleanValue { booleanValue: boolean; } type ValueType = StringValue | BooleanValue; var value: ValueType = { bo ...

Expanding the Warnings of React.Component

When I create a new class by extending React.Component in the following manner: export default class App extends React.Component<any, any > { constructor (props: React.ReactPropTypes) { super(props); } // other code } I encountere ...

Exploring Angular 4: Performing tests on a component containing another component

I am currently conducting testing on my Angular application: ng test Below is the content of my html file (page-not-found.component.html): <menu></menu> <div> <h4> ERROR 404 - PAGE NOT FOUND </h4> </div> Afte ...

How does [name] compare to [attr.name]?

Question regarding the [attr.name] and [name], I am utilizing querySelectorAll in my Typescript as shown below: this._document.querySelectorAll("input[name='checkModel-']") However, when I define it in the HTML like this: <input [name]="check ...

Could routerLinkActive be used to substitute a class rather than simply appending one?

I have a navigation bar icon that links to an admin route. I want this icon to change its appearance when on that specific route. To achieve this, I can simply replace the mdi-settings-outline class with mdi-settings, displaying a filled version of the sam ...

Tips for successfully mocking the AngularFire 2 service in a unit test

I am currently in the process of setting up unit tests for an Angular 2 application sample that utilizes AngularFire 2 authentication. The component I am working with is quite straightforward: import { Component } from '@angular/core'; import { ...

Ways to enhance a component by utilizing ChangeDetectorRef for one of its dependencies

I am in the process of expanding a component and one of its dependencies is ChangeDetectorRef export class CustomBgridComponent extends GridComponent implements OnInit { @Input() exportFileName: string = ''; constructor( ..., change ...

constrain a data structure to exclusively contain elements of a particular data type

interface Person { id:number, name:string } const someFunction(people: ???) => {...} Query: Can the people parameter be typeguarded to only allow an object with all properties matching a Person interface, similar to the following structure: pe ...

Is it possible for an ngrx effect to trigger the same action more than once?

I am working with an ngrx effect that needs to trigger multiple calls to the same ngrx action with different parameters. Here is my current approach: @Effect({dispatch: true}) public handleNodeWillReceiveFocus$ = this.actions$ .pipe( ofType( ...

Utilizing HttpClient in a static method service with Angular

One of my services contains a static method that I use for some initialization treatment. Within this method, I need to retrieve data from a web service @Injectable() export class FeaturesInitializationService { static allowedFeaturesModul ...

What is the best way to update the mat-tab when the routeParameters are modified?

I need to reinitialize the mat-tab-group in order to make the first tab active when there is a change in the routeParams. ts file: public index = 0; ngOnInit() { this.subscription = this.route.params.subscribe((routeParams: Params) => { // some ...

Error message: Unable to retrieve `__WEBPACK_DEFAULT_EXPORT__` before initializing Firebase Admin in a nx and nextjs application

My current project involves a Typescript Nx + Next.js App integrated with Firebase (including Firebase Admin). In this codebase, I have defined a firebase admin util as shown below - // File ./utils/FirebaseAdmin.ts // import checkConfig from './check ...

"Utilizing Angular's dynamic variable feature to apply ngClass dynamically

Looking for guidance on Angular - color change on button click. The loop binding is functioning well with dynamic variable display in an outer element like {{'profile3.q2_' + (i+1) | translate}}, but facing issues with [ngClass] variable binding ...