简体   繁体   中英

How do I populate a select form field in an Angular Reactive Form?

I am attempting to populate a select form field from an HTTP call. I get the data but cannot seem to set the value correctly utilizing a FormArray.

I have tried adjusting my object calls.references and even utilized array notation. I can get it to dump to the console, but not populate the select form field. So I am thinking that something is incorrect in my Angular code, but I have been unsuccessful in finding an example of this online. All of the documentation/tutorials I have come across do not have selects in the form.

public ngOnInit () {
    // Reactive form fields
    this.timecardForm = this.fb.group( {
      payBeginningDate: [ '2019-03-19', [ Validators.required ] ],
      payEndingDate: [ '2019-03-26', [ Validators.required ] ],
      payCategoriesTracked: this.fb.array( [ this.buildPayCategoriesTracked() ] ), // This is the one on which I am working
      overtimeCategories: '1',
      workHoursTracked: this.fb.array( [ this.buildWorkTracked() ] ),
      earnings: [ { value: null, disabled: true }, [ Validators.required ] ],
      totalHours: [ { value: null, disabled: true }, [ Validators.required ] ],
      totalEarnings: [ { value: null, disabled: true }, [ Validators.required ] ]
    } );
... // There is some more unrelated code
// Dynamically build payCategories row
  buildPayCategoriesTracked (): FormGroup {
    console.log( 'buildPayCategoriesTracked: ', this.timeEntry.payCategories ); // This prints to the console successfully
    return this.fb.group( {
      payCategories: [ this.timeEntry.payCategories, [ Validators.required ] ]
    } );
  }
<!-- The HTML in question... -->
<select formControlName="{{i}}"
        id="{{ 'payCategoriesTracked' + i }}"
        class="form-control"
        (change)="onRecordUpdated(timeCard, timecardDet)"
        [ngClass]="{'is-invalid': payCategoriesMessage }">
        <option value=null
                disabled
                selected
                hidden>--Select--</option>
        <option *ngFor="let payCategory of payCategories"
        [ngValue]="payCategoryDescription.payCategoryId">{{payCategoryDescription}}</option>
        </select>

I simply want my payCategories to populate my select form field.An example of one of the items returned is:

{payCategoryId: 9, description: "DUE FROM LIBRAR", payType: "Hourly", isActive: true}

So I want the select value to be the id and the description to display in the options tag.

Update I have changed my HTML as follows...

<div *ngFor="let payCategoriesTracked of payCategoriesTracked.controls;let i=index">
<mat-form-field>
    <mat-label>Pay Category</mat-label>
    <mat-select formControlName="i"
                (change)="onRecordUpdated(timeCard, timecardDet)"
                [ngClass]="{'is-invalid': payCategoriesMessage }">
        <mat-option *ngFor="let payCategory of payCategories"
                    [ngValue]="payCategory.value">
            {{payCategory.description}}
        </mat-option>
    </mat-select>
</mat-form-field>

Solution I figured it out...woot! Here is my solution...

<mat-form-field>
    <mat-label>Pay Category</mat-label>
    <select matNativeControl
            formControlName="payCategory">
        <option *ngFor="let payCategory of payCategories"
                [ngValue]="payCategory">
          {{payCategory.description}}
        </option>
    </select>
</mat-form-field>
// This is the FormControl, which is within a formgroup...
payCategory: new FormControl( this.buildPayCategories() )
// Pull in payCategories for select list from service
  buildPayCategories () {
    this.payCategories = this.timeEntry.payCategories;
    return this.payCategories;
  }

Use the 'this.timeEntry.payCategories' in the template to build the options, using ngFor

<option *ngFor="let payCategory of timeEntry.payCategories" [value]="payCategory.payCategoryId">{{ payCategory.description }}</option>

When building the Reactive Form, the first parameter in the FormControl should be the value from the select, in other words, the selected option. Example:

return this.fb.group( {
      payCategories: [ 2, [ Validators.required ] ]
    } );

Will match the option with value equals 2.

Clay, you need choose if you want a FormArray of FormGroup or a FormArray of FormControls.

A FormArray of FormGroups

  myForm=new FormGroup({
    myArray:new FormArray([
    new FormGroup({
      prop1:new FormControl('',Validators.required),
      prop2:new FormControl('',Validators.required)
    })
  ])
  })

<!--your form--->
<form [formGroup]="myForm">
    <!--a div with formArrayName--->
    <div formArrayName="myArray">
        <!--a div that iterate over "controls" and has [formGroupName]-->
        <div *ngFor="let group of myForm.get('myArray').controls;
                  let i=index" [formGroupName]="i">
            <!--your input using formControlName-->
            <input formControlName="prop1">
            <input formControlName="prop2"/>
         </div>
    </div>
</form>
{{myForm?.value|json}}
//Some like { "myArray": [ { "prop1": "", "prop2": "" } ] } 

A FormArray of FormControls

  myForm2 = new FormGroup({
    myArray: new FormArray([
      new FormControl('', Validators.required),
      new FormControl('', Validators.required)
    ])
  })

<!--your form--->
<form [formGroup]="myForm2">
    <!--a div with formArrayName--->
  <div formArrayName="myArray">
  <!--a div that iterate over "controls"-->
  <div *ngFor="let group of myForm2.get('myArray').controls;let i=index">
      <!--a unique element using [formControlName]-->
      <input [formControlName]="i">
  </div>
  </div>
</form>
{{myForm2?.value|json}}
//will be like  { "myArray": [ "", "" ] }

NOTE: I use directy new FormGroup and new FormArray , not FormBuilder. This avoid the need of inject the FormBuilder and, if you want, change the "change detection", but in sintax it's not much the difference

NOTE2: there are more differents ways to refered to a control in an array but I think this are the more clerest.

TIPs for make a form

  1. Always beging with {{form?.value|json}}. A formGroup exist independlty of the "inputs"
  2. we beging write <form *ngIf="form" [formGroup]="myForm"></form>
  3. At first use simple inputs
  4. Always need know what we want to get in form.value

Update About select. A select it's a input, only need give the formControlName to the "select". Well a select need an array of objects to show the options. If our array of object has two properties, "value" and "text" generally we use some like:

//If our arrayForm is an ArrayForm of FormGroups
<select formControlName="prop1">
  <options *ngFor="let item of options" [value]="item.value">
      {{item.text}}
  </option> 
</select>
//If our arrayForm is a Array of FormControls
<select [formControlName]="i">
  <options *ngFor="let item of options" [value]="item.value">
      {{item.text}}
  </option> 
</select>

First, take account there NO [selected] anywhere: it's not necesary. If we can selected a value we'll give value to the FormControl.

When we are using ReactiveForm, usually we subscribe to changeValue propertie of the control in spite of use (change), and we must be carefully: In options we can use [value] or [ngValue]. but the value can be a simple variable (a string or a number) or a complex object. If we are not using dropdop in cascate, the normal it's use only a simple variable.

Update I made changes and I think I am close because I see the data dumped to the screen via {{timecardForm?.value|json}}

I am receiving the control.registerOnChange is not a function error, which leads me to believe I have something referenced incorrectly. Here is my updated code...

<div class="col-2">
          <div formArrayName="payCategoriesTracked">
            <div *ngFor="let payCategory of payCategoriesTracked.controls; let i=index">
              <mat-form-field>
                <mat-label>Pay Category</mat-label>
                <mat-select [formControlName]="i"
                            (change)="onRecordUpdated(timeCard, timecardDet)"
                            [ngClass]="{'is-invalid': payCategoriesMessage }">
                  <mat-option *ngFor="let payCategory of payCategories"
                              [ngValue]="payCategory">
                    {{payCategory.description}}
                  </mat-option>
                </mat-select>
              </mat-form-field>
            </div>
          </div>

The data dump is as follows...

"payCategoriesTracked": [ { "payCategories": [ { "payCategoryId": 1, "description": "Converted Data ",...etc.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM