8

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.

4 Answers 4

11

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

Sign up to request clarification or add additional context in comments.

5 Comments

I attempted your solution but am getting a ts error that property push does not exist on type AbstractControl for the push method in addCollectionId()
collections should be (and is, in my plunker) declared as FormArray (collections: this.fb.array([])). If this is true, than FormArray has push() method (github.com/angular/angular/blob/…). Please create a plunker with your code and I will try to help.
you mentioned: "I am getting the error 'Argument of type 'string' is not assignable to parameter of type 'AbstractControl'", which means that FormArray has push() method which expects to get a new AbstractControl, while you tried to pass a string ID. So my only change was to create a new field control (this.fb.control(val)) with your ID set as its default value and then pass this new control to collections.push() method: collections.push(this.fb.control(val)).
We could go further and convert your md-select to multiple select and make it reactive form element, which would fill the form after user selection without need in addCollectionId() function. In general, there is no reason NOT TO USE reactive forms API when dealing with forms in ANGULAR
I updated my answer and plunker with reactive forms API only solution
2

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?

7 Comments

yeah i was worried about that. So I will have to work with and send the array outside of my formData?
Yes, but you are using reactive forms, correct? So you already have control of the layout of the data.
I am and that is true. Thanks for the input @DeborahK
See my updated answer with an example of how I've done this.
in that example this.product would represent the array of strings or string that were created, correct?
|
2
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.

Comments

0

It works for me when I use no FormGroup/FormGroupName but text controls have [formControlName]="controlIndex";

                <div *ngFor="let someFormArrayItem of someFormArray.controls; let i = index">
                  <input type="text" class="form-control" placeholder="type sthn..." [formControlName]="i">
                </div>

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.