Is your Angular form consistently coming back as invalid?

Within my Angular app, I have implemented a reactive form containing three checkboxes:

  1. The first checkbox allows users to choose whether they want to remain anonymous. If left unchecked, the name field will be visible. If checked, the name field will be hidden.
  2. The second checkbox enables users to reveal an email field. Once checked, the email field becomes visible; otherwise, it remains hidden.
  3. The third checkbox, labeled 'Agree', must be selected by the user in order to submit the form successfully.

Despite meeting all specified criteria, the submission form continues to be invalid. My aim is to ensure that submissionForm.valid returns true. Below is the code snippet for my submission form component:

I encountered an issue where all fields are returning null values, even after proper initialization. Could someone provide guidance on how to address this issue? Although I believe I have set up everything correctly, I am perplexed as to why the values are still showing as null.

import { Component, OnInit } from '@angular/core';
import { FormGroup, Validators, FormBuilder } from '@angular/forms';

@Component({
  selector: 'app-submission',
  templateUrl: './submission-form.component.html',
  styleUrls: [
    './submission-form.component.scss'
  ]
})
export class SubmissionFormComponent implements OnInit {
  submissionForm: FormGroup;
  formSubmitted = false; //Stores form status

  private nameValidators = [
    Validators.minLength(1),
    Validators.maxLength(50)
  ];

  private emailValidators = [
    Validators.maxLength(250),
    Validators.email
  ];

  constructor(private fb: FormBuilder) { }

  ngOnInit(): void { 
    this.createForm();
  }

  createForm(): void {
    this.submissionForm = this.fb.group({
      anonymous: [''],
      name: ['', this.nameValidators],
      contact: [''],
      email: ['', this.emailValidators],
      contentWarning: ['', [Validators.required]],
      title: ['', Validators.required],
      message: ['', [Validators.required, Validators.minLength(10), Validators.maxLength(10000)],
      agree: [false, [Validators.requiredTrue]]
    });
  }

  onSubmit() {
    if (this.submissionForm.get('anonymous').value == false) {
      this.submissionForm.get('name').setValidators(this.nameValidators.concat(Validators.required));
    } else {
      this.submissionForm.get('name').setValidators(this.nameValidators);
    }

    if (this.submissionForm.get('contact').value == true) {
      this.submissionForm.get('email').setValidators(this.emailValidators.concat(Validators.required));
    } else {
      this.submissionForm.get('email').setValidators(this.emailValidators);
    }

    this.formSubmitted = true; //The form has been submitted

    console.log(this.submissionForm.valid);

    if (this.submissionForm.valid) {
      console.log('submissionForm', this.submissionForm.value); //Proceed with form processing
    }
  }

  get anonymous() { return this.submissionForm.get('anonymous') };
  get name() { return this.submissionForm.get('name') };
  get contact() { return this.submissionForm.get('contact') };
  get email() { return this.submissionForm.get('email') };
  get contentWarning() { return this.submissionForm.get('contentWarning') };
  get title() { return this.submissionForm.get('title') };
  get message() { return this.submissionForm.get('message') };
  get agree() { return this.submissionForm.get('agree') };
}

Below is the relevant template code:

<section class="section">
    <div class="columns is-centered">
        <div class="column is-four-fifths">
            <h1 class="title">Submission Form</h1>
            <p class="success" *ngIf="formSubmitted">Successfully Submitted.</p>
            <form [formGroup]="submissionForm" (ngSubmit)="onSubmit()">

                <div class="field">
                    <!--Anonymous Checkbox-->
                    <label class="checkbox">
                        <input type="checkbox" 
                        formControlName="anonymous">
                        Remain anonymous
                    </label>
                </div>

                <div class="field" [hidden]="anonymous.value">
                    <!--Anonymous Name Field-->
                    <input class="input"
                        type="text"
                        placeholder="Name"
                        formControlName="name"
                        [ngClass]="{'form-submitted': formSubmitted}">
                    <div class="help-is-danger" *ngIf="name.invalid && !name.pristine">Maximum 50 characters allowed for the name field.</div>
                </div>

                <div class="field">
                    <!--Contact Checkbox-->
                    <label class="checkbox">
                        <input type="checkbox" formControlName="contact">
                        Contact me
                    </label>
                </div>

                <div class="field" [hidden]="!contact.value">
                    <!--Email Field-->
                    <input class="input"
                        type="text"
                        placeholder="Email"
                        formControlName="email"
                        [ngClass]="{'form-submitted': formSubmitted}">
                    <div class="help-is-danger" *ngIf="email.invalid && !email.pristine">
                        <p *ngIf="email.errors.email">A valid email address is required.</p>
                    </div>
                </div>

                <div class="field">
                    <!--Content Warning Field-->
                    <input  class="input"
                        type="text"
                        placeholder="Content Warnings"
                        formControlName="name"
                        [ngClass]="{'form-submitted': formSubmitted}">
                    <div class="help-is-danger" *ngIf="contentWarning.invalid && !contentWarning.pristine">Please provide content warnings.</div>
                </div>

                <div class="field">
                    <!--Title Field--> 
                    <input  class="input"
                        type="text"
                        placeholder="Title"
                        formControlName="title"
                        [ngClass]="{'form-submitted': formSubmitted}">
                    <div class="help-is-danger" *ngIf="title.invalid && !title.pristine">Title is a mandatory field.</div>
                </div>

                <div class="field">
                    <!--Message Text Area-->
                    <textarea  class="textarea"
                        type="text"
                        placeholder="Your Submission"
                        formControlName="message">
                    </textarea>
                    <div class="help-is-danger" *ngIf="message.invalid && !message.pristine">
                        <p *ngIf="message.errors.minlength">Message should contain at least 10 characters.</p>
                    </div>
                </div>

                <div class="field">
                    <!--'Agree' Checkbox-->
                    <label class="checkbox">
                        <input type="checkbox" 
                            formControlName="agree">
                        Agree
                    </label>
                    <div class="help-is-danger" *ngIf="submissionForm.hasError('required', 'agree') && !agree.pristine">You must check 'Agree' to proceed with the submission.</div>
                </div>

                <!--Submit Button-->
                <button type="submit" class="button is-danger is-outlined" [disabled]="!agree.valid && !submissionForm.valid">Submit</button> 
            </form>
        </div>
    </div>
</section>

This application uses Angular 9 along with the Bulma framework.

Answer №1

When dealing with fields that have specific rules only when visible, and the visibility state depends on other controls' values, custom validators are necessary. There are two ways to implement them: directly apply the custom validators to the controls or apply them to the form itself. While both methods can be verbose, I find applying the validators to the form easier to understand.

Check out the Stackblitz demo

It's important to note that this answer addresses your validator requirement specifically and may not necessarily be the optimal solution for achieving the desired user experience.

Replace the validators for these two fields with custom validators applied to the form:

createForm(): void {
  this.submissionForm = this.fb.group(
    {
      ...
      name: [''], // <= no validators here
      ...
      email: [''], // <= no validators here
      ...
    },
    { validators: [validateName, validateEmail] }
  );
}

Create your custom validators:

function validateEmail(formGroup: FormGroup): ValidationErrors {
  const anonymousCtrl = formGroup && formGroup.get('contact');
  if (!anonymousCtrl || !anonymousCtrl.value) {return null;}

  const control = formGroup.get('email');

  const regex = /^([a-zA-Z0-9_\-\.]+)@([a-zA-Z0-9_\-\.]+)\.([a-zA-Z]{2,5})$/;
  const emailMalformed = !regex.test(control.value);
  const emailRequired = !control.value;

  let error = {};
  if (emailMalformed) error = { ...error, emailMalformed: true };
  if (emailRequired) error = { ...error, emailRequired: true };

  return Object.keys(error).length ? error : null;
}

function validateName(formGroup: FormGroup): ValidationErrors {
  const anonymousCtrl = formGroup && formGroup.get('anonymous');
  if (!anonymousCtrl || anonymousCtrl.value) {return null;}

  const control = formGroup.get('name');

  const nameMaxLength = control.value && control.value.length > 250;
  const nameRequired = !control.value;

  let error = {};
  if (nameMaxLength) error = { ...error, nameMaxLength: true };
  if (nameRequired) error = { ...error, nameRequired: true };

  return Object.keys(error).length ? error : null;
}

Update your template to display errors on the controls when they occur:

<input formControlName="name">
<div class="help-is-danger" 
     *ngIf="nameInvalid && !name.pristine">
  Name should be 50 characters or less.
</div>

...


<input formControlName="email">
<div class="help-is-danger" 
     *ngIf="emailInvalid && !email.pristine">
  A valid email is required.
</div>

Implement the getters nameInvalid and emailInvalid:

get nameInvalid() {
  return (
    this.submissionForm.getError('nameRequired') ||
    this.submissionForm.getError('nameMaxLength')
  );
}

get emailInvalid() {
  return (
    this.submissionForm.getError('emailRequired') ||
    this.submissionForm.getError('emailMalformed')
  );
}

Answer №2

Even if you attempt to conceal these fields, the form still requires values to meet the validation rules.

In order to deactivate the validator in the name and email fields, you must disable the reactive form field

if (this.submissionForm.get('anonymous').value == false) {
  this.submissionForm.get('name').enable()
}
else{
  this.submissionForm.get('name').disable();
}

It is preferable to implement this logic in an rxjs manner

this.submissionForm.get('anonymous').valueChanges.subscribe(
  value => {
    if (value == false) {
      this.submissionForm.get('name').enable()
    }
    else{
      this.submissionForm.get('name').disable();
    }
  }
)

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

Encountering an issue while constructing an Angular library project. The 'ng serve' command runs smoothly on local environment, but an error message stating

I recently developed an npm package called Cloudee. While it functions perfectly locally, I encounter an issue when attempting to deploy it. The error message states: 'Unexpected value 'CloudyModule in /home/hadi/dev/rickithadi/node_modules/cloud ...

Angular's HostListener triggers twice when the browser's back button is clicked

In my Angular application, I have implemented a @HostListener that triggers when the back button on the browser is clicked. However, I have noticed that the event handler seems to be firing twice - prompting two dialogue boxes when clicking 'Ok'. ...

Creating templates for both classes and individual objects is an essential part of object-oriented programming

I am working on a simple game project using TypeScript. My goal is to utilize interfaces to implement them in classes and pass them as arguments for creating new instances of a class. interface ObjectConstructor { element: HTMLElement; x_pos: numbe ...

Issue encountered when attempting to access disk JSON data: 404 error code detected

I am attempting to retrieve JSON data from the disk using a service: import { Product } from './../models/Product'; import { Injectable } from '@angular/core'; import { Observable } from 'rxjs'; import { HttpClient } from &apo ...

Every key must be a string or number; received a function instead

I encountered a peculiar situation while working with Cucumber: Scenario Outline: Protractor and Cucumber Test InValid Given I have already...... When I fill the <number> .... Examples: | number|... | 3 |... |4 |... Moreover, I ...

Error message: Angular 12 - Event emitter variable is not defined

I'm currently diving into Angular, specifically working with version 12.0.1 and TypeScript 4.3.4. I'm stumped as to why my event emitter is showing up as undefined. Any suggestions or insights? Here's the error message that keeps popping up ...

ES2015's IterableIterator: Demystifying its Purpose

One thing that confuses me is when I implement the interface array, there are methods that return an IterableIterator. I understand that the IterableIterator extends from Iterator, which makes sense to me. interface IterableIterator<T> extends Iterat ...

Struggling to extract specific information from a json array using Angular, my current approach is only retrieving a portion of the required data

My JSON structure is as shown in the image below: https://i.stack.imgur.com/5M6aC.png This is my current approach: createUIListElements(){ xml2js.parseString(this.xml, (err, result) => { console.log(result); this.uiListElement = result[ ...

Firestore security measures verify modification of document keys

Is there a way to determine which key in a document has been modified in the Firestore security rules? And then verify it against the changed object? I'm struggling with this concept and finding it difficult to test. Appreciate any help! - Ryann ...

Changing a base64 string to an image within an angular 4 environment

My current task involves cropping an image using ng2-image cropper, but the result is in base64 format. I need to convert it to an image source so that I can send it to a server. Despite searching, I haven't been able to find anything compatible with ...

How to retrieve an object of type T from a collection of classes that extend a common base type in Typescript

In my current project, I have a class named Foo that is responsible for holding a list of items. These items all inherit from a base type called IBar. The list can potentially have any number of items. One challenge I am facing is creating a get method in ...

Encountering an installation issue with Angular 2 data table

Is there a solution for resolving unmet peer dependencies during the installation of Angular 2 data table? Here is the issue I am encountering while trying to install it. https://i.sstatic.net/HcirY.png ...

Guide on incorporating file uploads in an Angular 6 project

I am currently working on creating a component where I have implemented a file upload function in a child component and am attempting to use that component's selector in another one. Here is my project structure: - upload : upload.component.html up ...

What causes the intermittent failure of auto-import in VS Code?

My experience with auto-completing in VS Code has been inconsistent when it comes to auto-importing. Sometimes it successfully imports, but other times it fails, even with both local and node_modules imports. This issue occurs specifically in TypeScript fi ...

The issue of Eslint flagging a no-unused-vars error when actually using an interface in a

Currently, I'm working with Vue along with Vue CLI and Typescript. I have imported an interface from a Vuex file and utilized it for type annotation in mapState. However, I am encountering an error flagged by eslint. 'State' is defined ...

How can I prevent users from using the inspect element feature in Angular 4?

I have a collection of elements that I need to selectively display on a webpage. To achieve this, I am utilizing the following code snippet: [style.display]="!student.selected?'none':'block'" However, upon inspecting the element, a ...

Tips for indicating errors in fields that have not been "interacted with" when submitting

My Angular login uses a reactive form: public form = this.fb.group({ email: ['', [Validators.required, Validators.email]], name: ['', [Validators.required]], }); Upon clicking submit, the following actions are performed: ...

Simulating a PubSub publish functionality

I have been trying to follow the instructions provided in this guide on mocking new Function() with Jest to mock PubSub, but unfortunately I am facing some issues. jest.mock('@google-cloud/pubsub', () => jest.fn()) ... const topic = jest.fn( ...

Have you noticed the issue with Angular's logical OR when using it in the option attribute? It seems that when [(ngModel)] is applied within a select element, the [selected] attribute is unable to change

Within the select element, I have an option set up like this: <option [selected]=" impulse === 10 || isTraining " value="10">10</option> My assumption was that when any value is assigned to impulse and isTraining is set to true, the current o ...

Uncertainty regarding the integration process of `create-react-app --template typescript` with typescript-eslint

After creating a react project using npx create-react-app my-app --template typescript, I was surprised to find that typescript-eslint was already enabled by default. https://i.sstatic.net/1uijf.png Upon inspecting the eslint config within the package.jso ...