Angular 10 - Convert FormControl array values to Strings separated with commas

9.4k views Asked by At

EDIT 1 :

I modify my question to clarify my need.

I have a FormGroup that contains FormControl.

Among these FormControl, there is one that receives an array values.

What I'm looking for is if there is a solution to make the FormControl that receives an array values instead receive values in a comma separated string value (without the brackets [] ).

What I get : ( Array Values )

Array Values

What I want : ( String values separated with comma without brackets[] )

enter image description here

Link of the fork : Here

  • Thank you in advance for your help

Original Question :

I would like to convert the data received from a FormArray to Strings with commas.

I managed to do this in Console.log, but I don't know how to send the converted data to the FormGroup.

My TS file :

  accessoire: string;
  this.demandeDevis = this.formBuilder.group({
    accessoires: new FormArray([]),
    accessoire: this.accessoire,
  });

  onCheckboxChange(event) {
    const checkAcs: FormArray = this.demandeDevis.get(
      'accessoires'
    ) as FormArray;
    
    if (event.target.checked) {
      checkAcs.push(new FormControl(event.target.value));
      console.log(checkAcs.value.toString());
      this.accessoire = checkAcs.value.toString();
    } else {
      let i: number = 0;
      checkAcs.controls.forEach((item: FormControl) => {
        if (item.value == event.target.value) {
          checkAcs.removeAt(i);
              return;
        }
        i++;
      });
    }
  }

My HTML :

  <ul class="list-group list-group-flush">
    <li class="list-group-item p-3">
      Ventilateur cabine
      <label class="switch">
        <input type="checkbox" value="Ventilateur cabine" class="primary"
          (change)="onCheckboxChange($event)" />
        <span class="slider"></span>
      </label>
    </li>
    <li class="list-group-item p-3">
      Manoeuvre pompier
      <label class="switch">
        <input type="checkbox" class="primary" value="Manoeuvre pompier"
          (change)="onCheckboxChange($event)" />
        <span class="slider"></span>
      </label>
    </li>
    <li class="list-group-item p-3">
      Afficheur à tous les étages
      <label class="switch">
        <input type="checkbox" class="primary" value="Afficheur à tous les étages"
         (change)="onCheckboxChange($event)" />
        <span class="slider"></span>
      </label>
    </li>
    <li class="list-group-item p-3">
      Gong
      <label class="switch">
        <input type="checkbox" class="primary" value="Gong"
          (change)="onCheckboxChange($event)" />
          <span class="slider"></span>
      </label>
    </li>
    <li class="list-group-item p-3">
      Système de secours automatique
      <label class="switch">
        <input type="checkbox" class="primary" value="Système de secours automatique"
           (change)="onCheckboxChange($event)" />
        <span class="slider"></span>
      </label>
    </li>
  </ul>

When I follow these steps, I get null on " Accessoire " and array values on " Accessoires ".

What I'm looking for is to take the values chosen from a checkbox list and convert them to strings with commas. I don't want to send them in Arrays form.

Thank you in advance for your solutions.

EDIT 1 :

I used the first Option of @akash solution and it's working. I have just a little problem. When i get the values selected on an input, and I want to send it to a formControl ... I get the array form with brackets ... What I want is string form with commas. here's the link of my problem: https://stackblitz.com/edit/reactive-form-string-values

5

There are 5 answers

1
Eliseo On BEST ANSWER
<mat-select 
      [value]="toppings.value?toppings?.value.split(','):null" 
      (selectionChange)="toppings.setValue($event.value.join(','))"
       multiple>
...
</mat-select>

Remember a FormControl exist even you has no input. Well, you need rewrite all your code to received an string, some like this forked stackblitz

6
Akash On

Update 2:

To populate your form from db values, just initialize the control toppings like below (see this fork) -

toppingsValue: string = 'Extra cheese, Mushroom';
this.demandeDevis = this.formBuilder.group({
      toppings: [this.toppingsValue.split(',').map(a => a.trim())]
});

Update 1:

I think I misunderstood what you're trying to achieve. Please understand how angular FormGroup works. FormGroup has controls field which is what you pass as an object below. This object is a key-value where key is your control name (toppings) and value is your control (eg. FormControl, FormArray or FormGroup) -

this.demandeDevis = this.formBuilder.group({
      toppings: this.toppings 
});

So when you do this.demandeDevis.value, you get the object with same keys (toppings) and values in the corresponding control. You can use below code to get the comma separated values for toppings. Not sure if this is what you're looking for because this is obvious (see this fork) -

validateForm(){
    const toppingsValue = this.demandeDevis.controls['toppings'].value;
    console.log(toppingsValue.toString()); 
}

Original answer:

If you are open to different ways for how you'd like your UI to look like, use angular material

Option 1:

Use mat-select component. With below example, you don't need to handle selection/deselection of options. Please see this example on StackBlitz -

<mat-form-field appearance="fill">
  <mat-label>Toppings</mat-label>
  <mat-select [formControl]="toppings" multiple>
    <mat-option *ngFor="let topping of toppingList" [value]="topping">{{topping}}</mat-option>
  </mat-select>
</mat-form-field>

<div>
{{toppings.value}}
</div>

Option 2:

If you don't want to use dropdown, use mat-selection-list, see this example. Below example uses reference to mat-selection-list to get the list of selected options in the component -

<mat-selection-list #shoes (selectionChange)=selectionChange()>
  <mat-list-option [value]="shoe" *ngFor="let shoe of typesOfShoes">
    {{shoe}}
  </mat-list-option>
</mat-selection-list>
{{selectedValuesG}}
1
Owen Kelvin On

My solution will be to use Observable and transform the data whenever required.

// We define an Observable list of toppings
allToppings$: Observable<string[]> = of (['Extra cheese', 'Mushroom', 'Onion', 'Pepperoni', 'Sausage', 'Tomato']);

// We define our FormGroup
demandeDevis: FormGroup = this.formBuilder.group({
  toppings: [[]]
});

// Lets assume an initial topping in the form of string. Since MatSelect returns array, we will convert this to array and assign it to toppings selected
initialToppings$: Observable<string> = of('Onion, Pepperoni, Tomato'); 
initialToppingListArray$: Observable<string[]> = this.initialToppings$.pipe(
  map(toppingList => toppingList.split(',')),
  tap(toppings => this.toppings.patchValue(toppings))
);

// Next we define toppings and toppingsValue to extract toppings and toppings as a string from the form group
get toppings(): FormControl {
  return this.demandeDevis.get('toppings') as FormControl
};

get toppingsValue(): string {
  return this.toppings.value.join(',')
}

// Finally I will combine allToppings$ and initialToppingListArray$ to use one async pipe in the HTML
v$ = forkJoin([this.allToppings$, this.initialToppingListArray$]).pipe(
  map(([allToppings]) => ({ allToppings}))
)

In the HTML

<form [formGroup]="demandeDevis" *ngIf='v$ | async as v'>
<mat-form-field appearance="fill">
  <mat-label>Toppings</mat-label>
  <mat-select formControlName="toppings" multiple>
    <mat-option *ngFor="let topping of v.allToppings" [value]="topping">{{topping}}</mat-option>
  </mat-select>
</mat-form-field>

<button (click)="validateForm()" >
  Validate (console.log) 
  </button>
</form>


TOPPINGS VALUE: {{ toppingsValue }}

Now you can access the list as a string separated by using toppingsValue

See Sample on Stackblit

0
Owen Kelvin On

You will need to create a custom control

See Working Solution

app-string-select.ts

import { Component, forwardRef, OnInit } from "@angular/core";
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from "@angular/forms";
import { Observable, of } from "rxjs";

@Component({
  selector: "app-string-select",
  templateUrl: "./string-select.component.html",
  styleUrls: ["./string-select.component.css"],
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => StringSelectComponent),
      multi: true
    }
  ]
})
export class StringSelectComponent implements ControlValueAccessor {
  value: string[] = [];
  allToppings$: Observable<string[]> = of([
    "Extra cheese",
    "Mushroom",
    "Onion",
    "Pepperoni",
    "Sausage",
    "Tomato"
  ]);
  onChange: any = () => {};
  onTouched: any = () => {};
  constructor() {}
  writeValue(value: string): void {
    this.value = value.split(",");
  }
  registerOnChange(fn: any): void {
    this.onChange = fn;
  }
  registerOnTouched(fn: any): void {
    this.onTouched = fn;
  }
  emitChange() {
    this.onChange(this.value.join(","));
  }
}

app-string-select.html

<mat-form-field appearance="fill">
  <mat-label>Toppings</mat-label>
  <mat-select [(ngModel)]="value" (ngModelChange)='emitChange()' multiple>
    <mat-option *ngFor="let topping of allToppings$ | async" [value]="topping">{{topping}}</mat-option>
  </mat-select>
</mat-form-field>

select-multiple-example.ts

import {ChangeDetectionStrategy, Component} from '@angular/core';
import {FormControl, FormGroup, FormBuilder} from '@angular/forms';
import {Observable, of } from 'rxjs';
import {tap } from 'rxjs/operators';

/** @title Select with multiple selection */
@Component({
  selector: 'select-multiple-example',
  templateUrl: 'select-multiple-example.html',
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class SelectMultipleExample {

  demandeDevis: FormGroup = this.formBuilder.group({ toppings: ['']});

  initialToppings$: Observable<string> = of('Onion,Pepperoni,Tomato').pipe(
    tap(toppings => this.toppings.patchValue(toppings))
  )

  get toppings(): FormControl {
    return this.demandeDevis.get('toppings') as FormControl
  };

  constructor(private formBuilder: FormBuilder) {}

  validateForm(){
    const valuesForm = this.toppings.value;
    console.log(valuesForm); 
  }
}

select-multiple-example.html

<form [formGroup]="demandeDevis" *ngIf="initialToppings$ | async">
<app-string-select formControlName='toppings'></app-string-select>

<div>
   <button (click)="validateForm()" >
    Validate
  </button>
</div>
</form>
0
mr.vea On

You can do something like this here

 ngOnInit() {
  this.initDemandeDevis();
  this.toppings.valueChanges.subscribe(value => {
    this.demandeDevis.patchValue({
      toppingsToString: value.length > 0 ? value.join(",") : ""
    });
  });
 }

 initDemandeDevis() {
   this.demandeDevis = this.formBuilder.group({
     toppingsToString: "",
     accessoires: null
   });
 }

You will have to split it back up to populate the toppings array, but I am not sure if you need it.