Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

FormArray without FormControls and just strings Angular 2

I have two string arrays on my backend that I have to fill with either a single string or multiple strings. However they are not key value pairs. I am trying to push a string into one of the arrays but an running into the fact that I do not and cannot specify a control: value pair.

my formArray looks like

collections: new FormArray([]),

my html to select the strings

              <md-select placeholder="Collection">
            <md-option (click)="addCollectionId('one')" value="Local">Local</md-option>
            <md-option (click)="addCollectionId('two')" value="Music">Music</md-option>
            <md-option (click)="addCollectionId('three')" value="Performing Arts">Performing Arts</md-option>
            <md-option (click)="addCollectionId('four')" value="Sports">Sports</md-option>
            <md-option (click)="addCollectionId('five')" value="Restaurants">Restaurants</md-option>
          </md-select>

and my logic to add the strings to the formArray looks like:

  addCollectionId(id: string) {
const control = <FormArray>this.createCardForm.controls['collections'];
control.push(id);

}

I am getting the error 'Argument of type 'string' is not assignable to parameter of type 'AbstractControl'.

Since I cannot push a control: value pair and only string/strings how can I push strings to the array while still staying in my overall form?

Any help/tips/suggestions would be much appreciated.

like image 830
Brian Stanley Avatar asked Sep 11 '17 19:09

Brian Stanley


3 Answers

You can use Reactive Forms API to achieve this, also I recommend to use angular formBuilder:

    export class SelectOverviewExample {
      createCardForm: FormGroup;
  
      foods = [
        {value: 'steak-0', viewValue: 'Steak'},
        {value: 'pizza-1', viewValue: 'Pizza'},
        {value: 'tacos-2', viewValue: 'Tacos'}
      ];
  
      // inject form builder
      constructor(private fb: FormBuilder) {
        // add collections form array to your form
        this.createCardForm = this.fb.group({
          collections: this.fb.array([]),
        });
      }
  
      // function which pushed new value to collections array
      addCollectionId(val) {
        const collections = this.createCardForm.get('collections');
        // add only once
        if (!collections.value.includes(val)) {
          collections.push(this.fb.control(val));
        }
      }
    }

this way all your selected values will be added to the form and will be available under createCardForm.value.collections array.

here is HTML:

<md-select placeholder="Favorite food">
  <md-option [disabled]="createCardForm.value.collections.includes(food.value)" 
             *ngFor="let food of foods" 
             [value]="food.value" 
             (click)="addCollectionId(food.value)">
    {{ food.viewValue }}
  </md-option>
</md-select>

<pre>{{ createCardForm.value | json }}</pre>

here is updated plunker forked from https://material.angular.io/components/select/overview

UPDATE

here is reactive form only solution, without call to addCollectionId() function. Add reactiveCollections field to the form group:

    constructor(private fb: FormBuilder) {
      this.createCardForm = this.fb.group({
        collections: this.fb.array([]),
        reactiveCollections: null
      });
    }

Add form group and control names to the md-select:

<form [formGroup]="createCardForm">
  <md-select placeholder="Favorite food" 
             multiple="true" 
             formControlName="reactiveCollections">
    <md-option *ngFor="let food of foods" [value]="food.value">
      {{ food.viewValue }}
    </md-option>
  </md-select>
</form>

Plunker is updated as well

like image 147
Andriy Avatar answered Sep 19 '22 10:09

Andriy


The purpose of a FormArray is to contain a set of FormControls or FormGroups so that you can dynamically add input elements to a HTML form. I don't think that is what you are intending to do and that a normal array may suit your purpose.

This is from the docs:

Tracks the value and validity state of an array of FormControl, FormGroup or FormArray instances. A FormArray aggregates the values of each child FormControl into an array. It calculates its status by reducing the statuses of its children.

For example, if one of the controls in a FormArray is invalid, the entire array becomes invalid. FormArray is one of the three fundamental building blocks used to define forms in Angular, along with FormControl and FormGroup.

I assume you already have a data model that holds all of your data. Here is mine for example:

/* Defines the product entity */
export interface IProduct {
    id: number;
    productName: string;
    productCode: string;
    tags?: string[];
    releaseDate: string;
    price: number;
    description: string;
    starRating: number;
    imageUrl: string;
}

Notice that it has an array of tags.

Your addCollectionId method could then update your data model directly.

Before saving, you can do something like this:

let p = Object.assign({}, this.product, this.productForm.value);

It creates a new object, assigns it to the values from my product instance (which would have my tag array properties set) and then overwrites any properties from my form. So it basically updates my local product model properties with any form properties.

Then the save code would save p in this example.

Make sense?

like image 45
DeborahK Avatar answered Sep 22 '22 10:09

DeborahK


export class SelectOverviewExample {
  createCardForm: FormGroup;

  // inject form builder
  constructor(private fb: FormBuilder) {
    // add collections form array to your form
    this.createCardForm = this.fb.group({
      collections: this.fb.array([]),
    });
  }

  // get method which return the formArray as control
  get collections(): FormArray {
    return this.createCardForm.get('collections') as FormArray;
  };

  // function which pushed new value to collections array
  addCollectionId(val) {
    // Instead of this use get property
    //const collections = this.createCardForm.get('collections');

    // add only once
    if (!this.collections.value.includes(val)) {
      this.collections.push(this.fb.control(val));
    }
  }
}

I also got "Property 'push' does not exist on type 'AbstractControl'" error and I found the solution for this, only by removing const and added get method for collection.

like image 23
Aniket Avhad Avatar answered Sep 21 '22 10:09

Aniket Avhad