Setting checkbox values using patchValue in Angular programming

I'm facing an issue with reusing my create-form to edit the form values. The checkbox works fine when creating a form, but when I try to edit the form, the values don't get updated on the checkbox. Below is the code snippet that I have been working on:

<div class="form-row">
  <div class="form-group col-sm-6">
    <label>Authorized To</label>
    <br>
    <label *ngFor="let choice of authorized; let i=index">
      <input type="checkbox" [value]="choice" (change)="onCheckChange($event)" [checked]="checkedVal"
          formArrayName="authorized" type="checkbox[class.invalid]="! formGrowLicense.controls['authorized'].valid && formGrowLicense.controls['authorized'].touched ">
                            {{choice}}
     </label>
    <div *ngIf="!formGrowLicense.controls['authorized'].valid && (formGrowLicense.controls['authorized'].touched || isSubmitted)">
      <div class="invalid-feedback" style="display: block;">Please enter authorized to</div>
    </div>

  </div>
</div>

TypeScript

authorized: any = ['CULTIVATE', 'HARVEST', 'PROCESS']; //displaying list of checkboxes
constructor() {
  this.formGrowLicense = this.formBuilder.group({
      businessName: ['', Validators.required], 
      authorized: new FormArray([], [Validators.required])
       });
   } 
 getGrowLicense(id) {
    this.httpService.getGrowLicenseById(id).subscribe(
      response => {
        this.patchGrowLicense(response);
        this.checkedVal = response.authorisedTo; // storing response in this variable ['CULTIVATE','HARVEST']
      },

      (err: any) => console.log(err)
    );
  }
patch(licenseObj){
    this.formGrowLicense.patchValue({
      businessName:licenseObj.companyName,
      authorized: licenseObj.authorisedTo, // patch these two values as checked in checkboxes
      });
    }

 onCheckChange(event) {
    this.formArray = this.formGrowLicense.get(
      'authorized'
    ) as FormArray;

    /* Selected */
    if (event.target.checked) {
      console.log(event.target.value);
      // Add a new control in the arrayForm
      this.formArray.push(new FormControl(event.target.value));
    } else {
      /* unselected */
      // find and remove the unselected element
      let i = 0;

      this.formArray.controls.forEach((ctrl: FormControl) => {
        if (ctrl.value == event.target.value) {
          this.formArray.removeAt(i);
          return;
        }

        i++;
      });
    }
  }

Answer №1

There is a FormArray of FormControls that accepts values true/false and an array of strings within a fixed array. The first step is to transform the received array into an array of true/false values.

Initial Method

To begin, we will create a Form with a formArray. In order to manage a form array effectively, we need to create a getter that returns our formArray.

//our getter formArray
get authorizedArray()
{
    return this.formGrowLicense.get('authorized') as FormArray
}

ngOnInit()
{ //we create the formArray
this.formGrowLicense=new FormGroup({
businessName:new FormControl(),
authorized:new FormArray(this.authorized.map(x=>new FormControl(false)))
})
}

Notice that the formArray is created using

new FormArray(..here and array of formControls..)
. Each element of the 'authorized' array is mapped to a FormControl to create the formArray of FormControls.

To display a series of inputs, we use the following .html structure:

<form [formGroup]="formGrowLicense">
<input formControlName="businessName">
<!--we use formArrayName in a div-->
<div formArrayName="authorized">
<!--we iterate over autorizedArray.controls
remember our formArray getter function? -->
<div *ngFor="let control of authorizedArray.controls; let i=index">
<label><input type="checkbox" [formControlName]="i">{{authorized[i]}}</label>
</div>
</div>
</form>

We iterate over the formArrayControls and use the index to display the value of authorized[i] in the label.

Next step would be feeding the formArray with values when data is received. For example:

{
businessName:'my business name'
authorized:['CULTIVATE', 'PROCESS']
}

When receiving the data, we can do something like this:

this.formGroupLicense.patchValue(
{
businessName: data.businessName,
authorized: this.authorized.map(x=>data.authorized.indexOf(x) >=0)
}
)

This code snippet transforms the 'data.authorize' array (with 0, 1, 2, or 3 elements) into a new array of 3 elements containing only true/false values.

Lastly, in the submit function, we convert the [true,false,false] values to an array of strings:

submit(form)
{
if (form.valid)
{
const data={
businessName: form.value.businessName,
authorize: form.value.authorized.map(
(x, index)=>x?this.authorized[index]:null)
.filter(x=>x)
}
console.log(data) //Here we have the data to send to the service
}
}

We map [true,false,false] to ['CULTIVATE', null, null] and filter out elements that are null, resulting in ['CULTIVATE'].

Instead of using patchValue every time, we can create a function that generates a formGroup with the desired data:

createFormGroup(data:any=null)
{
data=data||{businessName:null, authorize:[]}
return new FormGroup({
businessName: new FormControl(data.businessName),
authorized: new FormArray(this.authorized.map(x=>new FormControl(data.authorized.indexOf(x)>=0)))
})
}

So, when data is received, we simply call:

this.formGroupLicense = this.createFormGroup(data)

Alternate Method

In this approach, we have a form structured like:

this.formGrowLicense=new FormGroup({
businessName:new FormControl(),
authorized:new FormControl()
})

YES! 'authorized' is a FormControl that stores an array. We can achieve a similar functionality to Material multi-select checkboxes using this method. Check out this link for more information about custom formControl usage.

An auxiliary array with 'name' and 'value' properties is used for demonstrating purposes:

authorizedAux: any = [{name:'CULTIVATE', value:true}, 
{name:'HARVEST', value:false},
{name:'PROCESS', value:true}]

We define two functions - setAuthorized() and parse() to handle the changes in FormControl values:

setAutorized(data: string[]) {
this.authorizedAux = this.authorized.map(x => ({
name: x,
value: data.indexOf(x) >= 0
}));
}

parse() {
const result=this.authorized
.map((x, index) => (this.authorizedAux[index].value ? x : null))
.filter(x => x);
return result.length > 0 ? result : null
}

The implementation in HTML involves using ngModel, ngModelChange, and ngModelOptions:

<form [formGroup]="form">
<input formControlName="businessName">
<div *ngFor="let control of authorizedAux">
<label>
<input type="checkbox"  
[ngModel]="control.value"
(ngModelChange)="control.value=$event; form.get('authorized').setValue(parse())"
[ngModelOptions]="{standalone:true}"
>{{control.name}}</label>
</div>
</form>

Remember to call setAuthorized() when receiving new data. Both approaches can be viewed in this Stackblitz link!

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

The AnimationRendererFactory ahead-of-time (AOT) compilation is encountering an issue where it is unable to access the property 'create

I am encountering an issue while trying to compile my Angular4 app AOT. The error message that I am stuck on is: TypeError: Cannot read property 'createRenderer' of undefined at AnimationRendererFactory.createRenderer (http://localhost:8080/cwp/ ...

The list in Ionic 3 search does not appear after clearing the search bar

Can anyone assist me with my search bar issue? I'm able to display words on the list, but once I clear the search bar, nothing shows up. Here is a snippet of my code: In my home.html file: <ion-searchbar (ionInput)="getItems($event)" [showCancelB ...

Exploring the possibilities of TypeScript/angularJS in HTTP GET requests

I am a beginner in typescript and angular.js, and I am facing difficulties with an http get request. I rely on DefinitelyTyped for angular's type definitions. This is what my controller code looks like: module game.Controller { 'use strict& ...

Disable and grey out the button while waiting for the Observable to broadcast successfully

component.html <button mat-raised-button color="primary" type="submit"> <mat-icon>account_box</mat-icon> <span *ngIf="!loading">&nbsp;&nbsp;&nbsp;Register</span> <span * ...

The payload from the Axios POST request is failing to reach its destination endpoint

I have two Express servers up and running - a gateway and an authentication service. I am facing an issue where the payload I set in the body of a request from my gateway to the authentication server never seems to arrive, despite there being no apparent C ...

Trouble arises when attempting to import React JSX project/modules from npm into an AngularJS TypeScript module

In the process of developing a proof-of-concept React framework/library, I aim to create a versatile solution that can be utilized in both React and AngularJS applications. To achieve this goal, I have initiated two separate projects: - sample-react-frame ...

Directing users to varying pages based on a particular criteria

As we continue to develop our application, we are creating various pages and need to navigate between them. Our current framework is Next.js. The issue we are facing involves the Home page: when transitioning from the Home page to another page (such as pa ...

Handling Promises in Angular 1 App using Typescript ES6/2015

Currently, I am working on an Angular 1.5 application that utilizes Typescript. My main concern is finding the most efficient way to handle ng.IPromise compared to Promise (ES6 promise). Personally, I would prefer to solely deal with the ES6 Promise type. ...

Angular array of considerable size

Dealing with a massive array of 30,000 items can be quite daunting, especially when it comes in as a stream using grpc-web. Currently, I'm fetching the data from grpc.client() and populating an array before displaying it using *ngFor. However, I' ...

Parsing error encountered while trying to handle an unexpected token at line 214, character 33. It appears that an appropriate loader is missing to process this particular file type

I've been developing a Typescript React project for the past few months without any issues. However, things took a turn yesterday when I decided to run npm audit fix and npm audit fix --force in order to address some security concerns that appeared ou ...

Learn how to access nested arrays within an array in React using TypeScript without having to manually specify the type of ID

interface UserInformation { id:number; question: string; updated_at: string; deleted_at: string; old_question_id: string; horizontal: number; type_id: number; solving_explanation:string; ...

What's the process for deducing the default generic parameter from a function type?

Is there a way to extract the default type parameter of a function in order to make this statement compile successfully? const fails: string = "" as ReturnType<<T = string>() => T>; ...

Typescript - using optional type predicates

I'm looking to create a custom type predicate function that can accurately determine if a number is real and tighten the type as well: function isRealNumber(input: number | undefined | null): input is number { return input !== undefined && ...

The type '{ children: Element; }' cannot be assigned to the type 'IntrinsicAttributes & ReactNode'

Encountered this error: Type '{ children: Element; }' is not assignable to type 'IntrinsicAttributes & ReactNode'. export const withAppProvider = (Component: AppComponent) => { return function WrapperComponent(props: any) { ...

How to retrieve the displayed text of a selected option in an Angular 7 reactive form dropdown control instead of the option value

Is there a way to retrieve the displayed text of the selected value in a drop-down list instead of just the value when using reactive forms? This is my current script: <form [formGroup]="formGroup" formArrayName="test"> <ng-container matColu ...

Difficulty Determining Literal Types that Expand a Union of Basic Data Types

Below are the components and function I am working with: interface ILabel<T> { readonly label: string; readonly key: T } interface IProps<T> { readonly labels: Array<ILabel<T>>; readonly defaultValue: T; readonly onChange ...

Steps for downloading Excel or Zip files in Angular 4

Currently, my front end is built using Angular 4 and the back end is developed with Lumen 5.4. The task at hand requires me to export certain data as both an Excel and a zip file. I am utilizing the { saveAs } import from the package 'file-saver/Fil ...

TS2365: The '!== 'operator is not compatible with the types ""("" and "")""

function myFunction(identifier: string) { identifier = "("; }; let identifier: string = ")"; if (identifier !== '(') throw "Expected '(' in function"; myFunction(identifier); if (identifier !== ')') throw "Expected &a ...

Is it possible to utilize [key:string]: any in order to eliminate the requirement for an additional function when working

Currently, I am utilizing recompose within my React project. Specifically, I am leveraging its compose function which is defined as: function compose<TInner, TOutter>( ...functions: Function[] ): ComponentEnhancer<TInner, TOutter>; interf ...

What is the process for extracting the paths of component files from an Angular ngModule file?

I've been on the lookout for automation options to streamline the process of refactoring an Angular application, as doing it manually can be quite tedious. We're working on reducing our app's shared module by extracting components/directive ...