Search code examples
angularangular-forms

angular change an input element's valid condition


I have an angular application that has a form. I have an input field where the user can enter a string and click an add button, which then parses the input, validates it, and adds it the form's model. The input element's [(ngModel)] is bound to a transient variable property on the component. Upon successful validation, the transient variable's value gets cleared so the user can enter another item to be added to the model.

The input field that the transient variable is a required field since you cannot submit the form if you never added a new item to the model.

I want to change the valid condition of the input element so that when at least one new item was added to the model, the input element field can be left blank and I can successfully submit the form.

form.component.ts:

import { Component, OnInit } from '@angular/core';
import { Model } from './model';

@Component({
  selector: 'app-form',
  templateUrl: './form.component.html',
  styleUrls: ['./form.component.css']
})
export class FormComponent implements OnInit {
  model: Model;
  transientItem: string;

  constructor() { }

  get diagnostic() { return JSON.stringify(this.model); }

  ngOnInit() {
    this.model = new Model([]);
  }

  onSubmit() {
    //handle submit logic
  }

  addItem() {
    const items = this.transientItem.split(' ');
    if (items.length === 3) {
      const newItem = {
        itemName: items[0],
        itemValue: items[1],
        itemLabel: items[2]
      };
      this.model.items.push(newItem);
      this.transientItem = '';
    } else {
      alert('invalid item format');
    }
  }

}

form.component.html:

<form (ngSubmit)="onSubmit()" #myForm="ngForm">
  {{diagnostic}}
  <div class="form-group">
    <input type="text" class="form-control" required [(ngModel)]="transientItem" name="item" #item="ngModel" />
    <button type="button" (click)="addItem()">Add</button>
  </div>
  <button type="submit" [disabled]="!myForm.form.valid">Submit</button>
</form>

model.ts:

export class Model {
    constructor(
        public items: {
            itemName: string,
            itemValue: string,
            itemLabel: string
        }[]
    ) { }
}

Current behavior: Submit button is disabled if input's value is empty, but as soon as I enter something in the text field, I can submit the form...

Expected behavior: Only when my model has at least 1 item in its items property can I submit the form. If I have 1 item in the model, and I leave the input field blank, I can still submit the form, since an item was already added.


Solution

  • I almost always use ReactiveForms because the API offers lots of useful tools to build simple to complex forms. In your case, a solution could be something like:

    import { Component } from '@angular/core';
    import { FormControl, FormGroup, FormArray, Validators } from '@angular/forms';
    
    @Component({
      selector: 'my-app',
      template: `
        <form [formGroup]="form">
          <input formControlName="newItem">
          <button type="button" (click)="addItem()" [disabled]="!item.valid">ADD</button>
          <hr>
          <button type="button" (click)="submit()" [disabled]="!items.valid">SUBMIT</button>
          <hr>
          <ul>
            <li *ngFor="let item of items.controls; let i = index">{{ item.value }} - <button type="button" (click)="removeItem(i)">DELETE</button></li>
          </ul>
        </form>
      `
    })
    export class AppComponent  {
      form: FormGroup;
    
      get item() {
        return this.form.get('newItem') as FormControl;
      }
    
      get items() {
        return this.form.get('items') as FormArray;
      }
    
      constructor() {
        this.form = new FormGroup({
          newItem: new FormControl('', Validators.required),
          items: new FormArray([], Validators.required),
        })
      }
    
      addItem() {
        this.items.push(new FormControl(this.item.value));
        this.item.reset('');
      }
    
      removeItem(i: number) {
        this.items.removeAt(i);
      }
    
      submit() {
        console.log(this.form.value);
      }
    }
    

    Let's explain a bit what we are doing.

    We are creating a new FormControl that should be required for the ADD button to be enabled.

    newItem: new FormControl('', Validators.required)
    

    and in our HTML

    <button type="button" (click)="addItem()" [disabled]="!item.valid">ADD</button>
    

    we are also creating a new FormArray to store all the new items that we want to keep and at the same time we are using the required validator to use the status for our SUBMIT button

    items: new FormArray([], Validators.required)
    

    and in out HTML

    <button type="button" (click)="submit()" [disabled]="!items.valid">SUBMIT</button>
    

    Yes, our entire form will not be VALID but we don't care. When you hit the SUBMIT button we extract the items using this.form.value['items'] which will be an array of items that we've added.

    Here is also a stackblitz.