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

Ensure that you are completely logged out of all browser tabs when one tab has been successfully logged out

Just started working with Angular 4 and have built an application with a login page and home page. Upon successful login, I navigate to the home page. I noticed that if my application is open in multiple tabs and I log out from one tab, clicking on any ot ...

The function item$.take cannot be found, resulting in a TypeError: "item$.take is not a function"

I am currently facing an issue with the take(1) function not working as expected with my Angular Firebase object. I have tried using valuechanges and snapshotchanges as alternatives, but they do not solve the problem for me due to other issues I encounter. ...

Properly write a function in Typescript that returns the initial property of an object

Looking for a solution to adjust the function below to match the property type: const firstProp = (object: Record<string, unknown>) => object[Object.keys(object)[0]]; Any thoughts on how to modify the function so its return type aligns with the ...

When creating a new instance of the Date object in Javascript, the constructor will output a date that is

In my project using TypeScript (Angular 5), I encountered the following scenario: let date = new Date(2018, 8, 17, 14, 0); The expected output should be "Fri Aug 17 2018 14:00:00 GMT-0400 (Eastern Daylight Time)", but instead, it is returning: Mon Sep ...

Typescript encounters difficulty locating the Express module

My venture into creating my debut NodeJS application has hit a roadblock. Following advice from multiple blogs, I have been attempting to build my first nodejs app in typescript by following the steps below: npm install -g express-generator npm install - ...

Tips for adding and verifying arrays within forms using Angular2

Within my JavaScript model, this.profile, there exists a property named emails. This property is an array composed of objects with the properties {email, isDefault, status}. Following this, I proceed to define it as shown below: this.profileForm = this ...

Transferring an array of data across different screens within an Ionic 2 application

I am a newcomer to Ionic 2 and I am encountering difficulties when it comes to passing data between pages. Within my Home.ts file, there exists a global array containing certain numbers that have been calculated. My intention is to transfer this array to m ...

Issues with Angular2 causing function to not run as expected

After clicking a button to trigger createPlaylist(), the function fails to execute asd(). I attempted combining everything into one function, but still encountered the same issue. The console.log(resp) statement never logs anything. What could be causing ...

Tips for ensuring the Google Maps API script has loaded before executing a custom directive on the homepage of an Angular website

Issue - I am facing issues with Google Maps autocomplete drop-down not working on my website's main page even after parsing and loading the Google Maps API script. The problem seems to be a race condition on the main page of my website, specifically i ...

Troubles with input handling in Angular

I've been diving into Traversy Media's Angular crash course recently. However, I've hit a roadblock that I just can't seem to get past. The problem arises when trying to style the button using a specific method. Every time I save and pa ...

Steps for adjusting the status of an interface key to required or optional in a dynamic manner

Imagine a scenario where there is a predefined type: interface Test { foo: number; bar?: { name: string; }; } const obj: Test; // The property 'bar' in the object 'obj' is currently optional Now consider a situatio ...

Learn how to dynamically enable or disable the add and remove buttons in the PrimeNG PickList using Angular 2

I'm currently learning Angular 2 and I'm working on creating a dual list box using PrimeNG's pickList component (https://www.primefaces.org/primeng/#/picklist). Within the pickList, I have table data with 3 columns, along with ADD and REMO ...

Guide to Automatically Updating Angular Component When Service Data Changes

I am currently working on an Angular application that features a sidebar component displaying different menu items based on the user's data. The sidebar should only display an option for "Empresas" if the user has not created any company yet. Once a c ...

What is the best way to refresh my mat-tree component without triggering a re-render?

Using a BehaviorSubject, my mat tree is able to redraw and initialize itself based on changes in the datasource. I keep an observable updated with data that I use to dynamically update the tree generated by a FlatTreeControl. The code is straightforward ...

Function modifies global variable

What could be causing the global variable to change when using the function write_ACK_ONLY()? I'm passing the array rxUartBuffer to write_ACK_ONLY() as data = new Array(20), but upon checking the Log Output, it seems that the function is also modifyin ...

Encountering difficulties when attempting to start a new project in Angular

I am encountering an issue while trying to create new in Angular, using version 6 and Node.js v8.11 Here is the console log: Unable to save binary /home/amit/demo/node_modules/node-sass/vendor/linux-x64-57 : { Error: EACCES: permission denied, mkdir ...

Encountering Gyp Errors During NPM Install in an Angular 5 Project

I encountered some gyp errors when trying to run "npm install" in my Angular 5 project. I'm not sure why these errors are occurring; perhaps someone here has seen similar issues before. https://i.stack.imgur.com/mMRO4.png I attempted various trouble ...

Encountering unexpected errors with Typescript while trying to implement a simple @click event in Nuxt 3 projects

Encountering an error when utilizing @click in Nuxt3 with Typescript Issue: Type '($event: any) => void' is not compatible with type 'MouseEvent'.ts(2322) __VLS_types.ts(107, 56): The expected type is specified in the property ' ...

Library of Functions in Angular 2

As I develop my application, I'm considering creating a functions library that can be utilized by various components. What would be the most effective approach to achieve this? Should I create a service containing all the functions, or rely on classes ...

Creating synchronous behavior using promises in Javascript

Currently, I am working with Ionic2/Typescript and facing an issue regarding synchronization of two Promises. I need both Promises to complete before proceeding further in a synchronous manner. To achieve this, I have placed the calls to these functions in ...