简体   繁体   中英

Duplicating forms with logic

I have created a dynamic form field that I add and remove through a callback function on buttons DelBtn() to remove item and AddBtn() to add item

Each of this form fields have a value input

I also have a totalval field. I expect the sum of values in all the value field not to exceed the totalval . If it does we display an a error message and if the value equals we make the reason form field appear.

Example:

If I have totalValue = 100 . Now lets say I have my first form field value = 90 .

Then I duplicate the form and in the next field set value = 10 then the reason field should appear because 90 + 10 = 100 . As the totalValue has been reached the reason form field should appear and the add button should be disabled .

If in the second attempt if the user tries to enter a value more than 10 then an error message should be shown.

Below is my current code

In my TS File I have

  ischecks: boolean = true;
  formsArray = [""];
  count: number = 0;
  totalval: number = 100;

  ngOnInit(): void {}
  constructor() {}

  clickCount(): void {
    this.count++;
  }

  DelBtn = delIndex => this.formsArray.splice(delIndex, 1);
  AddBtn = () => this.formsArray.push("");

HTML

<h2> Form</h2>

<pre style="font-weight: bolder;font-family:verdana;margin-left: 35px;">Total Value:{{totalval}}       </pre>
<div *ngFor="let i of formsArray; let a = index">

    <div>
        <form>
            <table>
                <div>
                    <label for="fname">Value:</label><br>
                    <input type="text" id="fname" name="fname" ><br>
                    <tr>
                        <td>
                            <button type="button" class="btn btn-outline-success"
        style="border-radius:40px;margin-left: 50px" (click)="DelBtn(a)" ><span class="fa fa-plus"></span>Delete</button>
                        </td>
                    </tr>
                    <tr>
                </div>
            </table>
        </form>
    </div>
</div>

<div *ngIf=ischecks style="margin-left:35%">
    <label for="fname">Reason:</label><br>
    <input type="text" id="fname" name="fname" ><br>
</div>
    <br>
    <button type="button" class="btn btn-outline-success"
            style="border-radius:40px;margin-left: 50px;margin-bottom: 30%;" (click)="AddBtn()"  ><span class="fa fa-plus"></span>Add</button>

https://stackblitz.com/edit/angular-ivy-3pbdwv?file=src%2Fapp%2Fapp.component.html

Note: If you do not understand the question feel free to ask me in comments adn I am working in angular(typescript)

This will be difficult to achieve with basic Javascript . Below approach uses ReactiveForm approach

We Follow the below steps

  • Add the ReactiveFormsModule to the imports array of the module
@NgModule({
  imports:[ ReactiveFormsModule, ... ],
  • Inject the FormBuilder class
constructor(private fb: FormBuilder) {}
  • Define the form
myForm = this.fb.group({
  totalVal: [100],
  formsArray: this.fb.array([this.fb.control("", { validators: [Validators.required] })]),
  reason: ["", [Validators.required]]
}, { validators: [sumMatches] });

We have added a cusom validator sumMatches . We will use this to check whether the sum of the total value has been matched

function sumMatches(control): ValidationErrors | undefined {
  const totalVal = Number(control.get("totalVal").value);
  const formsArrayTotal = control
    .get("formsArray")
    .value.reduce((a, b) => Number(a) + Number(b), 0);
  if (formsArrayTotal !== totalVal) {
    return {
      sumMismatch: true
    };
  }
  return;
}
  • Next we define helper getter functions to extract properties from the formGroup
  get sumMismatch(): boolean {
    return this.myForm.hasError('sumMismatch')
  }
  get arrayFullyFilled() {
    return !this.formsArray.controls.some(item => item.errors)
  }
  get formsArray() {
    return this.myForm.get("formsArray") as FormArray;
  }
  get totalVal() {
    return this.myForm.get("totalVal") as FormControl;
  }
  • We also need to amend the functions to add and remove items from the formArray
  DelBtn = delIndex => this.formsArray.controls.splice(delIndex, 1);
  AddBtn = () => this.formsArray.push(this.fb.control(""));
  • Finally we can implement the formGroup in the html
<h2> Form</h2>

<span class='totalVal'>Total Value:{{ totalVal.value }}</span>

<form [formGroup]='myForm'>
    <ng-container formArrayName='formsArray'>
        <table *ngFor="let item of formsArray.controls; let i = index">
            <tr>
                <td>
                    <div>
                        <label [attr.for]="'fname' + i">Value:</label><br>
                        <input type="number" [formControlName]="i" type="text" [id]="'fname' + i" name="fname" ><br>
                </div>
                </td>
                <td>
                    <button type="button" class="btn btn-outline-success"
        s (click)="DelBtn(i)" ><span class="fa fa-plus"></span>Delete</button></td>
            <tr>

        </table>
    </ng-container>
    <div *ngIf='!sumMismatch && arrayFullyFilled'>
        <label for="fname">Reason:</label><br>
        <input type="text" id="fname" name="fname" ><br>
</div>
        <br>
        <button type="button" class="btn btn-outline-success"
       (click)="AddBtn()"  ><span class="fa fa-plus"></span>Add</button>
        <br>
  <span class="error" *ngIf="sumMismatch && myForm.touched">Total Value Mismatch</span>
</form>

I have extracted css to own file

.totalVal {
  font-weight: bolder;
  font-family: verdana;
}
.btn-outline-success {
  border-radius: 40px;
  margin-left: 50px;
}
.error {
  color: red;
}

See this Demo

Edit 1 - How Does the validator work?

To understand this we look at how we build our form group. We defined a structure that produces a value in the form

  {
    totalVal: 100,
    formsArray: [''],
    reason: ''
  }

By defining our form group as this.fb.group({... }, {validators: [ sumMatches ] } the form group with the above value will be passed to the sumMatches function

In the sumMatches we will have a something like a formGroup with the value

  {
    totalVal: 100,
    formsArray: ['50', '20', '10'],
    reason: ''
  }

In the above we simply extract the 100 from the formGroup using control.get('totalVal').value same to formArray . Since formArray value will be an array then we can use reduce function to sum this.. We finally compare this and return null if they match and an Object if they do not match.

With the above approach, angular reactive forms will update the value of the form valid status based on what is provided by the user. We can hence leverage this valid status to update the UI

arrayFullyFilled()

get arrayFullyFilled() { 
  return !this.formsArray.controls.some(item => item.errors) 
}

The above code tries to find if the user has filled ALL the inputs in the array. In our array we get all the controls, check if some of them have errors and if any has error return false otherwise return true. This is made possible considering that in my formGroup I had made the formControls as required using Validators.required validation

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM