Errors abound in Angular's implementation of custom reactive form validators

When validating a form for a call center, the fields are usually required to be filled in a specific order. If a user tries to skip ahead, I want to raise an error for multiple fields. I have discovered a method that seems to work as shown below:

  export const recordValidator: ValidatorFn = (control: FormGroup): ValidationErrors | null => {

    if(!firstField.value && !secondField.Value && !thirdField.value)    
    {
      firstField.setErrors({ "firstFieldError" : true});
      secondField.setErrors({ "secondFieldError" : true});

      return {"firstFieldError" : true, "secondFieldError" : true};

    }
  }

Both firstField and secondField display errors correctly.

After reading the documentation, I realized that ValidationErrors is simply a map of errors without any methods. I tried casting an existing map to ValidationErrors like this:

  export const recordValidator: ValidatorFn = (control: FormGroup): ValidationErrors | null => {

    if(!firstField.value && !secondField.Value && !thirdField.value)    
    {
      firstField.setErrors({ "firstFieldError" : true});
      secondField.setErrors({ "secondFieldError" : true});

      let errorMap = new Map();

      errorMap.set("firstFieldError",true);
      errorMap.set("secondFieldError",true);

      let errorValidators:ValidationErrors = errorMap;

      return errorValidators;

    }
  }

However, this method does not raise any errors.

The template structure I am using is:

<mat-form-field>
  <input formControlName="firstField" type="datetime-local" placeholder="First Field" [errorStateMatcher]="errorMatcher" matInput>                        
      <mat-error *ngIf="Form.errors?.firstFieldError">
      First field error!
      </mat-error>
</mat-form-field>

I need help in understanding why the first method works and the second does not.

Answer №1

Hey Jim, the custom Validator you mentioned isn't working as expected. In order for it to work properly, you should return an object or null. Here's an example of how your validation function should look:

export const recordValidator: ValidatorFn = (control: FormGroup): ValidationErrors | null => {
    let invalid=false
    const error={}
    if (!control.value.firstField && control.value.secondField)
    {
        error.firstFieldError=true;
        invalid=true;
    }
    if (!control.value.secondField && control.value.thirdField)
    {
        error.secondFieldError=true;
        invalid=true;
    }
    return invalid?error:null;
  }

As you can see, we are accessing the values from the "control" (which is the formGroup) and creating an object with error properties based on certain conditions. You can check the errors in your .html file using:

{{form.errors|json}}

Just a heads up, it seems like the validator you provided may need some adjustments based on the description in your question. Feel free to reach out if you need further clarification.

Answer №2

I recently revisited this issue and successfully resolved it by utilizing resources from this blog and this reference from angular.io.

Below is a custom validator that returns a map of validation errors:

import { Injectable } from '@angular/core';
import { AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms';

@Injectable({ providedIn: 'root' })
export class ShiftTimeValidator {
    constructor() {}

    //validate(time: string, shifts: VehicleShift[]): ValidatorFn {
    validate(): ValidatorFn {

        return (
            control: AbstractControl,
        ): ValidationErrors => {

            let errors:ValidationErrors = {};

            // Skipping validation if form is pristine
            if (control.pristine) {
                null;
            }

            // Validating if time falls within existing shifts
            if(1 == 1){
                errors["inside-other-shift"] = true;
            }

            // Checking if start time is before end time
            if(1 == 1){
                errors["start-before-end"] = true;
            }

            // Verifying that end time is after start time
            if(1 == 1){
                errors["end-before-start"] = true;
            }

            // Ensuring new shift does not overlap with existing shifts
            if(1 == 1){
                errors["shift-overlap"] = true;
            }

            console.log(errors);


            return errors;
        };
    }
}

Instructions on integrating the validator into your form:

return this.fb.group({
  vehicleShiftId: [],
  vehicleId: [this.vehicle.vehicleId, Validators.required],
  shiftStartTimeString: [, [Validators.required, this.shiftValidator.validate()]],
  shiftEndTimeString: [, Validators.required, this.shiftValidator.validate()],
  vehicleStaff: staffArray
});

Guide for displaying error messages:

<mat-form-field>
    <input formControlName="shiftStartTimeString" type="time"
    name="shiftStartTime"
    placeholder="Shift start time" [errorStateMatcher]="errorMatcher" matInput>

        <mat-error *ngIf="shiftStartTime?.hasError('inside-other-shift')">
        Shift start time overlaps another shift.
        </mat-error>

        <mat-error *ngIf="shiftStartTime?.hasError('start-before-end')">
        Shift start time is after shift end time.
        </mat-error>

        <mat-error *ngIf="shiftStartTime?.hasError('end-before-start')">
        Shift end time is before shift start time.
        </mat-error>

        <mat-error *ngIf="shiftStartTime?.hasError('shift-overlap')">
        This shift overlaps another shift.
        </mat-error>
</mat-form-field>

While the approach of returning multiple errors from a single validator may be debated, this example demonstrates its feasibility.

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

Unable to submit data to PHP script using Angular 2

I am currently attempting to send a post request to a PHP script that contains the necessary data I require. Within home.component.ts: import { Component, OnInit } from '@angular/core'; import { UserComment } from '../definition/us ...

Connect the backend with the frontend using React.js

I am currently faced with the task of developing the front end for an existing backend project. The original front end was built using Angular, but I am opting to recreate it using React. However, I am unsure about how to establish a connection between t ...

Updating the displayed data of an angular2-highcharts chart

I am facing an issue with rendering an empty chart initially and then updating it with data. The charts are rendered when the component is initialized and added through a list of chart options. Although the empty chart is successfully rendered, I am strugg ...

Navigating through various Angular 7 projects in Express using JWT authentication and role-based routing

In my Angular 7 project, I have developed multiple applications for different roles such as admin, user, and editor. Each role has its own set of components and views. When a logged-in user accesses the application, they are directed to their respective r ...

When an item in the accordion is clicked, the modal's left side scroll bar automatically scrolls to the top. Is there a solution to prevent this behavior and

When I first load the page and click on the Sales accordion, then proceed to click on Total reported and forecasted sales, the scrollbar jumps back up to the top The marked ng-container is specifically for the UI of Total reported and forecasted sales He ...

Personalize the bootstrap classes within Angular app development

I'm currently utilizing bootstrap 4 and sass within my angular 6 application development. My goal is to create a customized nav tab in my application by slightly modifying the bootstrap nav-tabs and nav-link classes. However, the changes I make to th ...

Determining the best method for change detection in Angular 2: Choosing between Observable, EventEmitter, and Dot Rule

Managing change detection in Angular2 can be approached in three different methods that I have observed. Utilizing Observables @Injectable() export class TodosService { todos$: Observable<Array<Todo>>; private _todosObserver: any; ...

The integration of Angular and Node API within the IISNode directory structure is experiencing functionality issues

Read more about the question I have successfully set up my Node API and Angular component with IISnode. However, when accessing the application from the IIS server, I noticed that they are showing in different directories (see image below). Additionally, I ...

Tips for adjusting the angle in SVG shapes

I have designed an svg shape, but I'm struggling to get the slope to start from the middle. Can someone please provide assistance? <svg xmlns="http://www.w3.org/2000/svg" fill="none"> <g filter="url(#filter0_b_1_2556)"&g ...

Unexpected Behavior in Angular 12 Subscriptions

Having developed a ShoppingCart Service in Angular 12 with Firestore as the backend, my objective is to maintain the shopping functionality throughout the application. Upon page load, a method is triggered to check the presence of a "cartId" field in the l ...

Unusual class title following npm packaging

Currently, I am working on developing a Vue 3 library with TypeScript. We are using Rollup for bundling the library. Everything works as expected within the library itself. However, after packing and installing it in another application, we noticed that th ...

Guide to developing universal customized commands in Vue 3 using Typescript

I recently built my app using the Vue cli and I'm having trouble registering a global custom directive. Can anyone point out what I might be doing incorrectly here? import { createApp } from "vue"; import App from "./App.vue"; impo ...

The Angular 2 router is not compatible with using the same component but with different IDs

Currently utilizing the alpha8 router with 3 main routes: export const appRoutes: RouterConfig = [ { path: '', component: LandingComponent }, { path: 'blog', component: BlogComponent }, { path: 'posts/:id', compon ...

Issue: Unhandled promise rejection: SecurityError: To use screen.orientation.lock(), the page must be in fullscreen mode

While attempting to change the orientation of one of my pages in an Ionic 3 app, I encountered the following error. The code snippet below was used to change from portrait mode to landscape mode: ionViewDidEnter() { // this.statusBar.hide(); // // ...

Could a class instance be transformed into an object that holds the keys of its public properties in the interface?

For example, if we have a Person object defined like this: class PersonClass implements Person { private _name : string; private _age : number; get name() : string {return this._name} get age() : number {return this._age} constructor(name : strin ...

Issue with the code flow causing nested function calls to not work as expected

I'm experiencing an issue with my code: The problem arises when param.qtamodificata is set to true, causing the code to return "undefined" due to asynchronous calls. However, everything works fine if params.qtamodificata is false. I am seeking a sol ...

What is the most efficient way to modify a list within another list variable in Angular without impacting the parent list?

Here is a data list that I am working with: subscriberDataList -> wipEligibilityList -> dependentList dependentList -> wipEligibilityList -> eligibilityList wipEligibilityList[0] -> status - ...

The use of the "declare" keyword is prohibited within the `script setup` section of Vue

I need to integrate the function getCookie into my Vue file. This function is already defined in the HTML file where the Vue file will be injected. <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" ...

The null error occurs when rendering with React's state array

When I try to call an API that emits JSON, I am encountering an issue. I call the promise API function in componentDidMount, set the state, and then call it in the render method, but it always returns a null error. I need assistance, please. Interface fo ...

Exploring Query String Retrieval and Data Fetching in Ionic 2 using Angular 4's $

I am struggling to find the information I need, so I'm reaching out for help on how to send a request to a JSON api with parameters encoded in JavaScript. It is crucial that the parameters are properly encoded because the query could contain special ...