Is there a way to mark a template-driven form as invalid when a custom field validator fails in my Angular 15 application?

Currently, I am working on an Angular 15 app that utilizes a hand-coded JSON file along with the JSON server for performing CRUD operations on a "employees" JSON data.

One of the tasks at hand involves adding custom validation to a <select> element.

Within the employee-form.component.html file:

<form class="modal-content" #employeeForm="ngForm" (ngSubmit)="addEmployee(employeeForm)">
    <!-- Modal Header -->
    <div class="modal-header py-2">
      <h4 class="modal-title">New employee</h4>
      <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
    </div>

    <!-- Modal body -->
    <div class="modal-body py-2">
      <div *ngIf="isSuccess" class="text-center alert alert-success">
        Employee added successfully!
      </div>

      <div class="position-relative mb-1">
          <label class="form-label" for="firstName">First name</label>
          <input type="text" class="form-control" name="firstname" id="firstName" placeholder="First name" [(ngModel)]="firstname" #first_name="ngModel" required/>
          <div *ngIf="first_name.touched && first_name.errors?.['required']"  class="invalid-feedback">First name is required.</div>
      </div>

      ...
      (other form elements)
      ...

      <div class="position-relative mb-1">
        <label class="form-label" for="department">Department</label>
        <select class="form-select" name="department" id="department" [(ngModel)]="deptno" #emp_department="ngModel" required>
          <option value="-1">Pick a department</option>
          <option *ngFor="let department of departments" [value]="department.value">
            {{department.label}}
          </option>
        </select>
        <div *ngIf="emp_department.touched && emp_department.value == -1" class="invalid-feedback">You must pick a department</div>
      </div>

      ...
      (remaining form elements)
      ...

    </div>

    <!-- Modal footer -->
    <div class="modal-footer py-2">
      <button type="submit" class="btn btn-success" [disabled]="!employeeForm.valid">Add employee</button>
      <button type="button" class="btn btn-danger" data-bs-dismiss="modal">Cancel</button>
    </div>
</form>

Moreover, in the employee-form.component.ts file, there is a method named validateDepartment(form: NgForm), but it seems to be failing to validate correctly.

The main objective

The primary goal here is to ensure that a message saying "You must pick a department" appears over the select dropdown if no department is selected. Additionally, the form should remain invalid until a department is selected.

To address this issue, I have tried implementing the following solutions without success:

public validateDepartment(form: NgForm) {
   if (this.deptno == -1) {
    form.invalid;
   } 
}
public validateDepartment(form: NgForm) {
   if (this.deptno != -1) {
    form.valid;
   } 
}

You can check out a Stackblitz demo HERE.

Queries

  1. Why does the form appear valid while the select element remains invalid?
  2. What is the most effective way to resolve this issue?

Answer №1

In a select element, the "required" attribute should be placed on an option with [ngValue]="null" or [ngValue]="undefined"

<select
  class="form-select"
  name="department"
  id="department"
  [(ngModel)]="deptno"
  #emp_department="ngModel"
  required
>
  <option [ngValue]="undefined" selected hidden>Pick a department</option>
  <option *ngFor="let department of departments" [ngValue]="department.value">
    {{department.label}}
  </option>
</select>

PLEASE NOTE: If any input inside the form is invalid, then the entire form will also be invalid.

Update: To implement a custom validator in Template-driven forms, you need to create a directive that implements Validator, as described in the documentation.

@Directive({
  selector: '[isNot]',
  providers: [{provide: NG_VALIDATORS, useExisting: NotValueValidatorDirective , multi: true}]
})
export class NotValueValidatorDirective implements Validator {
  @Input('isNot') value:any = null;

  validate(control: AbstractControl): ValidationErrors | null {
    if (control.value==this.value)
        return this.value?{error:"can be "+this.value}:{error:"can be null";}
    return null
  }
}

Usage example:

<select isNot="-1">
   <option value="-1" hidden>Choose one</option>
   ...
</select>

Update2:The app.module file would look like this:

import { NgModule } from '@angular/core';
...
import { NotValueValidatorDirective } from './components/employee-form/ifnot.directive'

@NgModule({
  imports: [ BrowserModule, FormsModule, HttpClientModule, AppRoutingModule ],
  declarations: [
    FilterPipe,
    ...
    NotValueValidatorDirective 
  ]

The employee-form.component:

<div class="position-relative mb-1">
            <label class="form-label" for="department">Department</label>
            <select class="form-select" name="department" id="department" [(ngModel)]="deptno" #emp_department="ngModel" isNot="0">
              <!-- Changed default option value to 0 -->
              <option value="0" hidden>Pick a department</option>
              <option *ngFor="let department of departments" [value]="department.value">
                {{department.label}}
              </option>
            </select>
            <div *ngIf="emp_department.touched && emp_department.invalid" class="invalid-feedback">You must pick a department</div>
          </div>

Note that the directive remains the same but without the "standalone:true" setting.

Answer №2

To set the default gender, you have a couple of options:

-- You can simply add "checked" in the HTML code like this:-

<input type="radio" class="form-check-input" name="gender" id="male" checked value="male" />

Alternatively, you can also specify it through a component like so:-

<label *ngFor="let radiobutton of radioItems">
  <input type="radio" name="options" (click)="model.option = radiobutton"
  [checked]="radiobutton === model.option">{{radiobutton}}
</label> 

In the component-ts file, it would look something like this:-

export class AppComponent  {
  radioItems: Array<string>;
  model   = {option: 'Male'};

  constructor() {
    this.radioItems = ['Gender', 'Male', 'Female'];
  }
}

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

Guide on running a MySQL query in an asynchronous function in a Node.js application

Encountering some challenges while using MySQL in a node/express JS application. It seems that when trying to access the MySQL database within an asynchronous function, the code will 'skip over' the SQL query and run synchronously. This is the co ...

How can we refresh data obtained from a $http server call in AngularJS?

My controller is configured to load data from a server request initially, but I need to implement a refresh button that will send another request with updated parameters. The issue I'm facing is that the asynchronous nature of the request means the da ...

Testing the mongoose.connect callback method in Jest: A step-by-step guide

Currently working on a new Express application and utilizing Jest as the testing framework. All sections of code have been successfully covered, except for the callback of the mongoose.connect method: https://i.sstatic.net/SNrep.png I attempted to spy o ...

Create multiple instances of Object3D in THREE.js with various positions and rotations

Can I draw Object3D multiple times with different locations/rotations without cloning it? I want to avoid duplicating the object and instead just reference its geometries and materials. The Object3D in question is a Collada model (dae.scene) in my code. v ...

Comparing Selenium and Watir for JavaScript Testing within a Rails environment

In our experience with Rails apps, we have found success using RSpec and Cucumber. While Webrat is effective for non-AJAX interactions, we are now gearing up to write tests for our Javascript. We have used Selenium support in Webrat before, but I am inter ...

jqGrid: Efficiently edit a row inline by focusing on the specific cell clicked to enter edit mode

We have integrated jqGrid into our web application for data entry, allowing users to edit data inline and in rows. One of our customers has requested a more "Excel-like" experience, where clicking on a cell to switch a row to inline editing will focus spe ...

Generating dynamic divs in parallel

Despite encountering numerous posts related to the issue at hand, none of them have solved my specific problem. I am aiming for 3 divs to display in each row, but the current code is causing each div to show up on a new line vertically: JQuery ...

Making adjustments to regular expressions

In my asp.net application, I have a text box where users input a URL and I am using a regular expression for validation. The current regular expression looks like this: ^(ht|f)tp(s?)\:\/\/[0-9a-zA-Z]([-.\w]*[0-9a-zA-Z])*(:(0-9)*)*(&bsol ...

"An unusual blend of TWEENJS and threejs creating a mesmerizing

Having successfully created one animation loop with tweenjs, I am now eager to develop an infinite moving animation from top to bottom. However, I am facing some bugs in my code and require assistance! You can check out the code here. var group_posi ...

AWS Lambda encountered an issue: Task terminated unexpectedly prior to finishing the operation

I've scoured various Stack Overflow threads for solutions to my issue, but no luck so far. As a beginner in this field, I might not be implementing the suggested fixes correctly. Here's my code, and any help would be greatly appreciated. var mys ...

Is it possible for a memory leak to occur in node.js when using new Date()?

For this particular case, when updating an existing MongoDB document with new Date() causing a potential memory leak is a question that arises. One might wonder if allocating a new object with the new keyword necessitates manual deallocation to prevent lea ...

The configuration for CKEditor5's placeholder feature seems to be malfunctioning

I am currently experimenting with a customized version of CKEditor 5 known as BalloonBlockEditor. Below is the custom build output that I have created: /** * @license Copyright (c) 2014-2023, CKSource Holding sp. z o.o. All rights reserved. * For licens ...

"Experience the power of Vue.js 3.2 with Dynamic Component Knockout

I am trying to use a dynamic component to update my section, but when I click on my sidebar ('nav'), it doesn't change. Even though route.params.current changes, the component is not loaded. <template> <q-page class="contain ...

Managing checkbox inputs within a MEAN stack application

As someone who is brand new to the MEAN stack, I've been experimenting with saving an array of checkbox values and retrieving them from MongoDB. I've set up a clear schema for embedded arrays using Mongoose, but I'm struggling with how to in ...

What is the best way to extract items from another array that have approved IDs - is it through using map(), filter(),

I am unsure about the best approach to retrieve only the items (array with objects) that have been approved based on their id. Should I start by using map() and then filter(), or should I filter() them first and then map()? export class AppComponent { ...

What is the process for starting Nx serve for an application that has lint errors?

After receiving an nx Angular project, I noticed that there are lint rules in place. Executing nx run shell:lint displays all the errors and warnings specified in the .eslintrc.json configuration file. However, when running nx run shell:serve, the build ...

Is there a way for me to generate a preview thumbnail for my video?

Looking to add a preview effect to video thumbnails when users hover over them, displaying a series of frames from the video. Are there any jQuery plugins or tutorials available for creating this effect? ...

What is preventing me from generating a string for transform:translate within Angular.js?

Attempting a new approach here $scope.graph.transform = "transform: translate(" + $scope.graph.width + "," + $scope.graph.height + ");"; Despite my efforts <h3>transform: <span ng-bind="grap ...

What's the reason behind the data not being retrieved by $.ajax?

After attempting to validate a form and send the values using $.ajax, I encountered an error message stating "Undefined index: is_ajax". Why is the form_data not being received? What could be causing this issue and how can it be resolved? function validat ...

Ignoring page redirects in Node.JS

Directories are organized as follows: -public -landing -landing.html -Login -login.html -register -register.html -routes HTMLroutes APIroutes - server.js All commented code are attempts I have made without success if (bcry ...