Creating unique validations for Angular FormArray (ensuring that the sum of proportions equals 100%)

I implemented a formArray in Angular to manage the distribution of nominees, with fields for Name and Proportion. The plus button (+) adds a new row, while the (X) button deletes the current row. I am now looking to create validation logic that ensures the total proportion column always adds up to 100% (regardless of the number of nominee names).
Example:
No Name Proportion
1 Andy 100
(Total Proportion: 100%)
----------
Example 2:
No Name Proportion
1 Andy 60
2 Bruce 40
(Total Proportion: 100%)
----------
Example 3:
No Name Proportion
1 Andy 60
2 Bruce 20
3 Ciao 20
(Total Proportion: 100%)
----------

Below is my component.html code:

<h5>Nominees</h5>
                <div class="row">
                    <form novalidate [formGroup]="FormNominees">

                        <div clas="col-xs-12 form-group marL40">
                            <div formGroupName="itemRows">
                                <ng-container *ngIf="FormNominees.controls.itemRows!=null">
                                    <div *ngFor="let itemrow of FormNominees.controls.itemRows.controls; let i = index"
                                        [formGroupName]="i">
                                        <div class="row">
                                            <mat-form-field
                                                class="example-full-width d-block input-small-size col-sm-2.4"
                                                appearance="outline">
                                                <input matInput placeholder="Name" formControlName="name">
                                                <mat-error *ngIf="f9.name.touched && f9.name.errors?.required">It is mandatory
                                                </mat-error>
                                                <mat-error *ngIf="f9.name.touched && f9.name.errors?.pattern">Can only contain characters.
                                                </mat-error>
                                            </mat-form-field>

                                            // Other form field elements...

                                            <mat-form-field
                                                class="example-full-width d-block input-small-size col-sm-2.4"
                                                appearance="outline">
                                                <input matInput placeholder="Proportion"
                                                    formControlName="gratuityProportion">
                                                     <mat-error *ngIf="f9.gratuityProportion.touched && f9.gratuityProportion.errors?.proportionValidator">Total must be 100%.
                                                    </mat-error> 

                                            </mat-form-field>
                                            
                                            // Button elements...
                                        </div>
                                    </div>
                                </ng-container>
                            </div>
                        </div>

                    </form>
                </div> <br>

Below is my component.ts code:

        import { proportionValidator } from './proportion.validator';
        @Component({
          selector: 'app-component',
          templateUrl: './component.component.html',
          styleUrls: ['./component.component.scss']
        })
        export class GratuityComponent implements OnInit {
      FormNominees: FormGroup;
      TotalRow: number;
      itemFB: any;
    
    constructor(private fb: FormBuilder) {
    
   }
ngOnInit(): void {

this.FormNominees = this.fb.group({
      itemRows: this.fb.array([this.initItemRow()])
    });
  initItemRow() {
    this.itemFB =  this.fb.group({
      name: ['', [Validators.required, Validators.pattern('[a-zA-Z0-9. ]*' )]],
      relationship: ['', Validators.required],
      phone: ['', [Validators.required, Validators.pattern('[a-zA-Z0-9. ]*' )]],
      gratuityProportion: ['', [Validators.required, Validators.pattern('^[0-9]*$'), proportionValidator] ],
      gender:['', Validators.required],
      
      employeeUniqueId: '00000001-0001-0001-0001-000000000001'
    })
    return this.itemFB;
  }

  // Rest of the component.ts code...

}
    
    }

Now, for the proportion.validator.ts file, you can add the following code:

import { AbstractControl } from '@angular/forms';
    
export function proportionValidator(control: AbstractControl): { [key: string]: boolean } | null {
  let sum = 0;
  const controlArr = control.value;
  
  if(controlArr !== undefined && controlArr.length > 0){
     controlArr.forEach(item => {
       sum += parseInt(item.gratuityProportion);
     });

  return sum === 100 ? null : { proportionValidator: true };
}

Answer №1

Taken directly from the official documentation:

const array = new FormArray(
  [
    new FormControl('Nancy'),
    new FormControl('Drew')
  ], 
  {
   validators: myValidator //<- this is where you can add your custom validator
  }
);

In this specific scenario, the code will appear as follows:

    this.FormNominees = this.fb.group({
      itemRows: this.fb.array(
        [
          this.initItemRow()
        ],
        { 
           validators: proportionValidator 
        }
      )
    });

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

Difficulty encountered when attempting to invoke a public function that makes use of a private function in TypeScript

I'm relatively new to TypeScript and I've been trying to replicate a pattern I used in JavaScript where I would expose functions through a single object within a module (like "services"). Despite my efforts, I'm facing some issues when attem ...

Is there a way to update data from the backend without the need to refresh the window?

In my application, I have set up a route foo/:id/edit which utilizes several resolvers to make service calls. Following some interaction on this particular view, I aim to refresh the data retrieved from these resolvers without having to reload the entire ...

Do not allow the menu toggle to collapse upon clicking

Currently, I have implemented a menu with the .fadeToggle function on my website. There are buttons on the page that, when clicked, display a different section of information. While everything is functioning correctly and users can navigate through the s ...

Unfulfilled promises are left hanging

I've encountered a problem while writing a unit test for my Angular application using Jasmine with a mock service. The promise I am trying to run is not functioning as expected. Below is the service code: CreateItemController = $controller('Cre ...

The key to successful filtering in Next.js with Hasura is timing - it's always a step

I am fetching data from Hasura using useRecipe_Filter and passing the searchFilter state as a variable. It seems that every time I press a key, the UI updates with one keystroke delay before filtered data is passed to the results state. const SearchBar = ( ...

Error in Vue 3 Script Setup with Typescript: Implicit 'any' type for parameter 'el' in Template ref

Could someone help explain why I am receiving an error from TypeScript that says the following? error TS7006: Parameter 'el' implicitly has an 'any' type. ref="(el) => saveRef(index, el)". I am confident that the correct type is set ...

Update a div housing PHP code dynamically without needing to reload the entire webpage

I have a PHP script enclosed within a div that displays random text fetched from a file called random.txt. I am looking to automatically refresh this div every 15 seconds without reloading the entire page. I have come across various methods using AJAX or J ...

Can global scope be injected into a class instantiated in ES6, also known as a singleton?

Before I get started, I want to apologize in advance for the lengthy code that is about to follow. It may make this question seem a bit bloated, but I believe it's necessary for understanding my issue. Imagine we have a predefined MainModule: ' ...

Using the phrase "activating a 'sister' function"

I'm dealing with some code that resembles the following: var viewService = function () { ... return { ... ,isAbsolute: function (view) { ... } ... ,removeAbsoluteViews: function () { ... } } }; }; My goal is to ...

Turborepo users encountering peculiar errors with Remix integration

Currently, I am working on integrating a remix example for my library, react18-themes, and have a functional example available here. However, I am facing some unusual errors when attempting to set up the example in the monorepo. TypeError: Unknown file ext ...

Pause the for loop until all nested asynchronous database calls are completed

Currently, I am utilizing the listCollection method within mongodb to loop through each collection that is returned using a query with find. The issue arises when I attempt to construct an object within the loop that I intend to return with response.json ...

I stored a nested object in sessionStorage, but when I retrieve and parse it, I receive string values

Is it possible to return a complete object without having to parse each level repeatedly after parsing my object? Have I stored my data incorrectly using redux-persist? For instance, when I have a login form that sends credentials to the server and receiv ...

Generating HTML content from XML data with the help of JavaScript

Challenge: Attempting to display one question and its four corresponding answers at a time from an XML file. JavaScript code: var xmlDoc, quest, ans, i, n; xmlDoc = loadXMLDoc("questions.xml"); quest = xmlDoc.getElementsByTagName('main'); do ...

Transitioning from embedded browser to system browser in a Next.js / React.JS application

I'm currently dealing with an issue on my Next.js payment page and could really use some expertise. Here's the situation at hand: My payment page has a QR code that directs users to the payment page created with Next.js. When users scan this QR ...

What could be causing the submit button to reactivate before all form fields have been completed?

I have implemented the code snippet below to validate each field in my contact form using bootstrap-validator and an additional check through Google reCAPTCHA. You can view and test the form here. The submit button is initially disabled with the following ...

Tips for generating documentation using markdown within the docs directory on GitHub with the help of JavaScript

After browsing through numerous documentation websites, I have noticed that many of them share the same layout and features. This has led me to question whether there is a common library used by all these documentation sites. How do they achieve this unif ...

Creating personalized functions in Object.prototype using TypeScript

My current situation involves the following code snippet: Object.prototype.custom = function() { return this } Everything runs smoothly in JavaScript, however when I transfer it to TypeScript, an error surfaces: Property 'custom' does not ex ...

Troubleshooting issues with Vue CLI 4 and A-Frame: Finding solutions for

Recently, I've been working on creating a VR web app using Vue cli 4.2.3 and aframe.io. However, I encountered numerous error messages related to aframe's components. [Vue warn]: Unknown custom element: <a-scene> - did you register the com ...

Is it possible to use Next Image to load multiple images within a loop effortlessly?

I have the following array of template types: const TEMPLATE_TYPES = [ { name: 'Blog Post', type: 'blog-post', img: '/img1.png' },... ]; In a later stage, I'm iterating over TEMPLATE_TYPE ...

Exploring the capabilities of Swiper within the Angular 17 framework

Currently, I am delving into the world of Angular 17 for a new project and looking to incorporate Swiper for a dynamic carousel feature. Despite my best efforts, I have encountered some obstacles while attempting to make it work. Has anyone in this commun ...