Verification based on conditions for Angular reactive forms

I am currently learning Angular and working on creating a reactive form.

Within my HTML table, I have generated controls by looping through the data.

I am looking to add validation based on the following cases:

  1. Upon page load, the Save button should be disabled by default (which I achieved using
    [disabled]="!myform.valid"
    )
  2. The Save button should only enable when the user enters a value in any of the text boxes or selects a checkbox in that particular row. Selecting a checkbox and adding a value in a text box simultaneously is not allowed. Either the checkbox should be selected or the user can enter a value in any of the text-boxes.

I have attempted to achieve this with the following code snippet:

IsFormValid(){return (!!this.myfm.get('myform').value);}// but this always returns true.

Here is my code:

    myfm:FormGroup;
  ngOnInit() {
  debugger;
  this.myfm = this.fb.group({
  myform: this.fb.array([this.addformFileds()])
  }); 
}

 addformFileds(): FormGroup {
  return this.fb.group({
  NoTask: ['', Validators.required],// only check box is required either checkbox should click 
 or enter value in any text-boxes
                                    
  task1: ['', null],    
  task2: ['', null],
  task3: ['', null],
  task4: ['', null],
  task5: ['', null],
  task6: ['', null],
  task7: ['', null],
  task8: ['', null],
  task9: ['', null],
  task10: ['', null],
  task11: ['', null],
  task12: ['', null] 
});
}

 //adding more rows on click of Add More Rows button
addEntried():void{
this.idAddNewRow=true; //indicator that new rows are being created 
(<FormGroup>this.myfm.get('myform')).push(this.addEntried());
}

I know it's a bit tricky, still haven't found a solution. Any help on this matter would be greatly appreciated.

My component.html code:

 <form #frmImp='NgForm' [formGroup]="myfm"]>
        <div class="modal-content">
        <div class="modal-header" style="align-items: center"> 
          <button type="button" class="close" data-dismiss="modal" aria-label="Close">
            <span aria-hidden="true">&times;</span>
          </button>
        </div>
        <div class="modal-body">       
   <!--<table class="table-bordered table-responsive" style="overflow-x: true;"> to disable the scroll bar if screen resolution is less than 1920x1080-->
            <table class="table-bordered " >
      <thead>
          <tr style="border-bottom-style: solid"><td id="" class=""  colspan="15" style="text-align: center"><b>Imp Task Details</b></td>
          </tr> 
          <tr>
            <th style="text-align: center;"> </th>
            <th style="text-align: center;"> </th>
            <th style="text-align: center;">No Task</th>
            <th style="text-align: center;">Task 1</th>
            <th style="text-align: center;">Task 2</th>
            <th style="text-align: center;">Task 3</th>
            <th style="text-align: center;">Task 4</th>
            <th style="text-align: center;">Task 5</th>
            <th style="text-align: center;">Task 6</th>
            <th style="text-align: center;">Task 7</th>
            <th style="text-align: center;">Task 8</th>
            <th style="text-align: center;">Task 9</th>
            <th style="text-align: center;">Task 10</th>
            <th style="text-align: center;">Task 11</th>
            <th style="text-align: center;">Task 12</th> 
       
          </tr>
        </thead>
        <tbody formArrayName="myform" **ngFor="let frm of myfm.get('myform')['controls']; let i=index">
          <tr [[formGroupName]="i"]>
            <td></td>
            <td></td>
            <td><input [id]="'txt'+i" type="checkbox" formControlName="NoTask" style="margin-left: 30px;"/></td> 
            <td  ><input [id]="'txt'+i" type="tex" class="textboxFormat" formControlName="task1"   placeholder="0"></td>
            <td ><input [id]="'txt'+i" type="tex" class="textboxFormat"   formControlName="task2" ></td>
            <td ><input [id]="'txt'+i" type="tex" class="textboxFormat"   formControlName="task3" ></td>
            <td ><input [id]="'txt'+i" type="tex" class="textboxFormat"   formControlName="task4" ></td>
            <td ><input [id]="'txt'+i" type="tex" class="textboxFormat"   formControlName="task5" ></td>
            <td ><input [id]="'txt'+i" type="tex" class="textboxFormat"   formControlName="task6" ></td>
            <td ><input [id]="'txt'+i" type="tex" class="textboxFormat"   formControlName="task7" ></td>
            <td ><input [id]="'txt'+i" type="tex" class="textboxFormat"   formControlName="task8" ></td>
            <td ><input [id]="'txt'+i" type="tex" class="textboxFormat"   formControlName="task9" ></td>
            <td ><input [id]="'txt'+i" type="tex" class="textboxFormat"  formControlName="task10" ></td>
            <td ><input [id]="'txt'+i" type="tex" class="textboxFormat"  formControlName="task11" ></td>
            <td ><input [id]="'txt'+i" type="tex" class="textboxFormat"   formControlName="task12" ></td>  
          </tr>
           
        </tbody>
    </table> 
  </div>
        <div class="modal-footer">
          <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
          <button type="submit" class="btn btn-primary" *ngIf="isAvailable" (click)="addformFileds()">Add More Rows </button> 
          <button type="submit" class="btn btn-primary" (click)=SaveImpDetails(frmImp)>Save </button> 
        </div>
      </div>
    </form>


**component.ts** file code
SaveImpDetails(){
   this.baseSrvc.post(ApiResource.SaveImpDetails, 
    JSON.stringify(body)).subscribe((result=>{
if(result){
   this.alertService.showMessage("Success!", "Details has been 
    recorded 
    successfuly.")
 }if(result.isError){
 this.alert.showMessage("Error!! while saving details");
}
  }));
   }
   

Answer №1

Update:

Upon reviewing the question in its entirety, we were able to address it by introducing a new validator for each form item.

Below is an example to demonstrate this implementation (also available on Stackblitz):

  myfm: FormGroup;

  constructor(private fb: FormBuilder) {}

  ngOnInit() { this._generateForm(); }

  private _generateForm() {
    this.myfm = this.fb.group({
      myform: this.fb.array([this.addformFileds()]),
    });
  }

  addformFileds(): FormGroup {
    return this.fb.group(
      {
        NoTask: '',
        task1: '',
        task2: '',
        task3: '',
        task4: '',
        task5: '',
        task6: '',
        task7: '',
        task8: '',
        task9: '',
        task10: '',
        task11: '',
        task12: '',
      },
      { validator: [validateRow()] }
    );
  }

Additionally, here are the helper function and validator:


function validateRow(): ValidatorFn {
  const pairs: string[][] = [
    ['task1', 'task2'],
    ['task3', 'task4'],
    ['task5', 'task6'],
    ['task7', 'task8'],
    ['task9', 'task10'],
    ['task11', 'task12'],
  ];
  return (group: FormGroup): ValidationErrors | null => {
    if (group.get('NoTask').value) {
      return null;
    }

    // check the pair validities
    const invalidPair = pairs.find((pair) => {
      const firstTask = group.get(pair[0]).value;
      const secondTask = group.get(pair[1]).value;

      if ((firstTask && !secondTask) || (!firstTask && secondTask)) {
        return true;
      }

      return false;
    });

    // return pair validity error object
    if (invalidPair) {
      return { invalidPair };
    }

    if (!singleWhere(group, (item) => item.value)) {
      return {invalidRow: true};
    }

    return null;
  };
}

/**
 * find and return the first control for which
 * the `callback` function returns `true`
 *
 * loop over the `group` controls
 * and return the `control`
 * if the `callback` function returns `true` for the `control`
 *
 * @param group - is the form
 * @param callback - is the callback function
 *
 * @return `AbstractControl`
 */
 function singleWhere(
  group: AbstractControl,
  callback: (control: AbstractControl, index: number) => boolean
): AbstractControl {
  if (!group) {
    return null;
  }
  if (group instanceof FormControl) {
    return callback(group, 0) ? group : null;
  }

  const keys = Object.keys((group as FormGroup).controls);

  for (let i = 0; i < keys.length; i++) {
    const control = group?.get(keys[i]);

    if (singleWhere(control, callback)) {
      return group;
    }
  }

  return null;
}

Finally, in the template, add a disable attribute based on this condition:

[disabled]="myfm.invalid || myfm.untouched"

For the complete code example, please refer to this link: https://stackblitz.com/edit/angular-bootstrap-4-starter-vvimke?file=src/app/app.component.ts

Answer №2

Have you considered using a FormArray for organizing the tasks? By utilizing a formArray and implementing two key functions within it:

  //You can define your formArray as follows
  formArray=this.fb.array([0,1,2,3,4,5,6,7].map(_=>this.addformFields()))

  getGroup(i)
  {
    return this.formArray.at(i) as FormGroup
  }

  tasks(i)
  {
    return this.getGroup(i).get('tasks') as FormArray
  }

Your addformFields function would then look like this:

  addformFields(): FormGroup {
    return this.fb.group({
      noTask:false,
      tasks:this.fb.array([0,1,2,3,4,5,6,7,8,9,10,11].map(_=>this.fb.control(0)))
    },{validator:this.someValue()})
  }

The validator function is defined as:

  someValue(){
    return (control:AbstractControl)=>{
      const group=control as FormGroup;
      let valid=control.get('noTask').value;
      (control.get('tasks') as FormArray).controls.forEach(x=>{
        valid=valid || +x.value!=0
      })
      return valid?null:{error:"You should indicate one task or that there' not task"}
    }
  }

To control the FormArray in the HTML file:

<table class="form-group">
  <tr>
    <th>Section</th>
    <th *ngFor="let i of [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]">Task {{ i }}</th>
  </tr>
  <tbody>
    <tr
      *ngFor="let group of formArray.controls; let i = index"
      [formGroup]="getGroup(i)"
    >
      <td><input type="checkbox" formControlName="noTask" /></td>
      <ng-container formArrayName="tasks">
        <td *ngFor="let control of tasks(i).controls; let j = index">
          <input
            [attr.disabled]="getGroup(i).get('noTask').value ? true : null"
            [formControlName]="j"
          />
        </td>
      </ng-container>
    </tr>
  </tbody>
</table>

If you specify a .css style like the following:

tr.ng-invalid.ng-touched input{
  border:1px solid red;
}
tr.ng-invalid.ng-touched input:focus{
  border-color:   red;
  border-radius: .25rem;
  border-width: 2px;
  outline: 0;
  box-shadow: 0 0 0 .15rem rgba(255,0,0,.25);
}

Here's a StackBlitz link for reference.

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

Transforming JavaScript code with Liquid inline(s) in Shopify to make it less readable and harder to understand

Today, I discovered that reducing JavaScript in the js.liquid file can be quite challenging. I'm using gulp and typescript for my project: This is a function call from my main TypeScript file that includes inline liquid code: ajaxLoader("{{ &ap ...

Utilizing Session storage throughout an Angular 2 application

I currently store a session variable as a JSON value in my home.component.ts. This variable needs to be accessed in various locations within the application. This is my code snippet: .do(data => sessionStorage.setItem('homes', JSON.stringif ...

Input a value to request in the Mssql node

I am using angular2 and mssql to establish a connection with SQL Server. This is my code snippet: var express = require('express'); var app = express(); var sql = require("mssql"); // database configuration var config = { user: 'sa&ap ...

What could be causing jQuery to overlook this button?

Having some trouble with my angular, bootstrap, and jQuery setup. I can't get jQuery to select a button and trigger an alert when clicked: $('#some_button').click(function(e) { alert('testing'); }); <button id="some_but ...

What causes RxJS concat to generate distinct values when given an array as input instead of separate arguments?

According to the documentation for rxjs, the concat operator can accept individual observables as arguments or an array of observables. When using individual arguments, the concatenated observable behaves as expected, combining values in sequence. Here&apo ...

An error persists in PhpStorm inspection regarding the absence of AppComponent declaration in an Angular module

After creating a new Angular application, I am encountering the issue of getting the error message "X is not declared in any Angular module" on every component, including the automatically generated AppComponent. Despite having the latest version of the An ...

Check if the array includes a specific index

Below is the code snippet I am currently working with: <li *ngFor="let item of serviceList; let i = index;"> <button *ngIf="currentTools contains i" (click)="processService(item)"> Run Service</button> </li> Is t ...

TS1057: It is required that an async function or method has a return type that can be awaited

There was a recent Github issue reported on March 28th regarding async arrow functions generating faulty code when targeting ES5, resulting in the error message: TS1057: An async function or method must have a valid awaitable return type You can find t ...

Aurelia's navigation feature adds "?id=5" to the URL instead of "/5"

I have set up my Aurelia Router in app.ts using the configureRouter function like this: configureRouter(config, router: Router) { config.map([ { route: ['users', 'users/:userId?'], na ...

Angular 2: The unsubscribe property is not defined

I have been working on creating an ObservableTimer that counts up to a specific number. The logic for this is already in place, but I am encountering an issue when attempting to unsubscribe from it. When I try to unsubscribe, I receive an error message say ...

Is there a feature in Angular 2+ (specifically Angular 7) that allows for comparing code differences

Is there a code comparison component or plugin available for Angular 2+ (specifically Angular 7) that can compare two separate text files? In our current AngularJS application that we are upgrading, we currently use Ace-Diff and it works effectively. Howe ...

Unique TypeScript code snippets tailored for VSCode

Is it possible to create detailed custom user snippets in VS Code for TypeScript functions such as: someArray.forEach((val: getTypeFromArrayOnTheFly){ } I was able to create a simple snippet, but I am unsure how to make it appear after typing an array na ...

Jasmine : Techniques for monitoring a method callback using method.then()

Within my Angular 4.0.0 application, I have a method called in my component. This method is invoked within a service: this.myService.myMethod(param).then(any => { console.log("success case"); }) .catch(error => { console.log("error"); }); ...

Maintain the text layout when copying and pasting from the Angular Application

After noticing that copying and pasting text from an Angular Application to a text editor like Microsoft Word results in losing the original format, I decided to investigate further. An example I used was the angular material website: https://material.ang ...

Utilize Angular roles to sort and organize website data

Exploring the utilization of login roles in my Angular SPA application which operates solely on the client side, integrated with Spring Security and Spring Boot. I have concerns about potential unauthorized access by a skilled developer who could manipula ...

The jsPdf library performs well on medium-sized screens like laptops, but when downloaded from larger monitor screens, the PDF files do not appear properly aligned

In the code snippet below, I am using html2canvas to convert HTML to PDF. The PDF download works fine on medium screens, but when viewed on larger screens, some pages appear blank and the content order gets shuffled. How can I resolve this issue so that th ...

Tips for elegantly merging two Observables within an RXJS pipeline

I am working on developing a log viewer using Angular. Upon user entry, I aim to load historical logs and also begin monitoring for new logs. Users have the ability to filter logs using a simple form that emits a query object. Each time the query changes, ...

Can the Angular CLI be installed without using the command prompt?

Are there alternative methods for installing angular cli besides using the command line in cmd? I am encountering proxy setting issues when trying to install it using npm install -g @angular/cli (I am currently using a VPN for work) npm ERR! code ETIMEDOUT ...

Exploring the capabilities of Dynamic Route integration with Server Side components using Supabase in Next.js

export default async function Page({ searchParams, }: { searchParams?: { [key: string]: string | string[] | undefined }; }) { // const searchParams = useSearchParams(); const id = searchParams?.p ?? "aaa"; // default value is "1" ...

Discover the latest techniques for incorporating dynamic datalist options in Angular 13 with Bootstrap 5

React 17 Tailwind CSS dynamiclist.component.html <div> <label for="{{ id }}" class="form-label">{{ label }}</label> <input class="form-control" list="{{ dynamicListOptions }}" [i ...