greendaysbomb
greendaysbomb

Reputation: 404

Angular form is always returning invalid?

I have an Angular reactive form with three checkboxes:

  1. The first checkbox is to see if the user would like to remain anonymous. If the user leaves this unchecked, then the name field remains. If checked, the name field is hidden.
  2. The second checkbox is to display an email field. If checked, then the email field appears. If it remains unchecked, the email field does not appear.
  3. The third checkbox is an 'Agree' checkbox. The user must select this in order to submit the form.

My issue is that, even if all these requirements are met, the submission form is still invalid. I would like to see that my submissionForm.valid returns true. Here is my submission form component:

EDIT: thanks to the answer in the comments, I see that all my fields are returning null values. Could anyone please help me fix this as well? I feel that I am initializing everything correctly but I am still confused as to why the values are returning 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; //Holds status of the form

  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; //Form has been submitted

    console.log(this.submissionForm.valid);

    if (this.submissionForm.valid) {
      console.log('submissionForm', this.submissionForm.value); //Process the form
    }
  }

  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') };
}

Here is the accompanying 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">Submitted successfully</p>
            <form [formGroup]="submissionForm" (ngSubmit)="onSubmit()">

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

                <div class="field" [hidden]="anonymous.value">
                    <!--Checkbox for remaining anonymous-->
                    <input class="input"
                        type="text"
                        placeholder="Name"
                        formControlName="name"
                        [ngClass]="{'form-submitted': formSubmitted}">
                    <div class="help-is-danger" *ngIf="name.invalid && !name.pristine">Name should be 50 characters or less.</div>
                </div>

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

                <div class="field" [hidden]="!contact.value">
                    <!--Field for email-->
                    <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">Valid email is required.</p>
                    </div>
                </div>

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

                <div class="field">
                    <!--Field for title--> 
                    <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 required.</div>
                </div>

                <div class="field">
                    <!--Text area for message-->
                    <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 is required and should be at least 10 characters.</p>
                    </div>
                </div>

                <div class="field">
                    <!--Checkbox for agree-->
                    <label class="checkbox">
                        <input type="checkbox" 
                            formControlName="agree">
                        Agree
                    </label>
                    <div class="help-is-danger" *ngIf="submissionForm.hasError('required', 'agree') && !agree.pristine">Must check 'Agree' in order to submit.</div>	
                </div>

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

I am currently using Angular 9 and the Bulma framework.

Upvotes: 3

Views: 19651

Answers (2)

julianobrasil
julianobrasil

Reputation: 9377

Because you have fields that must follow some rules only when they are visible, and their visibility state depends on the value of other controls, you must write some custom validators. You have two ways of doing that: applying the custom validators directly to the controls, or applying them to the form. Both of them are kind of verbose and I'll put here the one I think is easier to understand, which is applying to the form.

Stackblitz demo

It's important to notice that I'm just answering your requirement for validators. It doesn't mean this is the best or easiest way to achieve the UX you want.

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

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

Write 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;
}

Fix your template to show the errors on the controls when they happen

<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>

Write 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')
  );
}

Upvotes: 5

Wahyu Fadzar
Wahyu Fadzar

Reputation: 126

even though you hide these field, the form still demand for value to met their validator rules.

to disable validator in name, email field you need to disable the reactive form field

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

it will be better to use this if else in rxjs way

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

Upvotes: 3

Related Questions