SBB
SBB

Reputation: 8990

Angular2 - Reactive Forms w/ Arrays of Data

I have a form setup in my component where a user can modify up to three shift containers which include start time, end time, and days of the week.

Without having to duplicate a bunch of code three times, I am trying to figure out the most dynamic way to approach this.

Here is my current HTML for a single shift:

<span formGroupName="slice" *ngFor="let slice of [1,2,3]">
   <table class="table table-condensed">
      <thead>
         <th></th>
         <th></th>
         <th></th>
         <th></th>
         <th></th>
         <th></th>
         <th></th>
         <th></th>
         <th style="text-align:center;">Enable</th>
      </thead>
      <tbody>
         <tr>
            <td class="col-md-2" style="text-align:center; vertical-align:middle">
               Time Slice {{ slice }}
            </td>
            <td>
               <select name="hour_start" class="form-control input-sm"  formControlName="hour_start">
                  <option value="{{ h }}" *ngFor="let h of hours">{{ h }}</option>
               </select>
            </td>
            <td>
               <select name="minute_start" class="form-control input-sm" formControlName="minute_start">
                  <option value="{{ m }}" *ngFor="let m of minutes">{{ m }}</option>
               </select>
            </td>
            <td>
               <select name="period_start" class="form-control input-sm" formControlName="period_start">
                  <option value="{{ p }}" *ngFor="let p of period">{{ p }}</option>
               </select>
            </td>
            <td style="text-align:center; vertical-align:middle;">-</td>
            <td>
               <select name="hour_end" class="form-control input-sm" formControlName="hour_end">
                  <option value="{{ h }}" *ngFor="let h of hours">{{ h }}</option>
               </select>
            </td>
            <td>
               <select name="minute_end" class="form-control input-sm" formControlName="minute_end">
                  <option value="{{ m }}" *ngFor="let m of minutes">{{ m }}</option>
               </select>
            </td>
            <td>
               <select name="period_end" class="form-control input-sm" formControlName="period_end">
                  <option value="{{ p }}" *ngFor="let p of period">{{ p }}</option>
               </select>
            </td>
            <td style="vertical-align:middle; text-align:center;"><input type="checkbox" (change)="toggleSlice(slice, true)"></td>
         </tr>
         <tr>
            <td class="col-md-2"></td>
            <td colspan="7">
               <table class="table table-condensed" formGroupName="days">
                  <tbody>
                     <tr>
                        <td *ngFor="let d of days">
                           <div class="checkbox">
                              <label>
                              <input type="checkbox" formControlName="day_{{ d }}"> {{ d }}
                              </label>
                           </div>
                        </td>
                     </tr>
                  </tbody>
               </table>
            </td>
         </tr>
      </tbody>
   </table>
</span>
<!-- Time Slice -->
</span>

Here is my component:

this.transitionForm = this.fb.group({
            shift: this.fb.group({
                slice: this.fb.group({
                    hour_start: { value: '1', disabled: true },
                    minute_start: { value: '00', disabled: true },
                    period_start: { value: 'AM', disabled: true },
                    hour_end: { value: '1', disabled: true },
                    minute_end: { value: '00', disabled: true },
                    period_end: { value: 'AM', disabled: true },
                    days: this.fb.group({
                        day_Su: { value: '', disabled: true },
                        day_M: { value: '', disabled: true },
                        day_Tu: { value: '', disabled: true },
                        day_W: { value: '', disabled: true },
                        day_Th: { value: '', disabled: true },
                        day_F: { value: '', disabled: true },
                        day_Sa: { value: '', disabled: true }
                    })
                })
            })
        }); 

On other small details is that each shift row contains an enable/disable checkbox so I can choose to utilize the other two shift containers if needed, otherwise, they are disabled.

Now using something like jQuery, I could have done this by just getting the parent element of this checkbox and handling each set of data (shift) in that manner. My goal here though is to not need to use jQuery and figure out what reactive forms can offer for the situation.

Question:

How can I go about forming these three time slices (shifts) into a more dynamic setup using reactive forms rather than hard coding a slice for each one. Can reactive forms handle this?

Upvotes: 1

Views: 412

Answers (1)

0mpurdy
0mpurdy

Reputation: 3353

Yes, you can use Form Arrays to dynamically add and remove sections of a reactive form.

There is a good blog post covering this here, I've included some of the relevant pieces below

Component

ngOnInit() {
  // we will initialize our form here
  this.myForm = this._fb.group({
      name: ['', [Validators.required, Validators.minLength(5)]],
      addresses: this._fb.array([
        this.initAddress(),
      ])
    });
  }

initAddress() {
    // initialize our address
    return this._fb.group({
      street: ['', Validators.required],
      postcode: ['']
    });
  }

addAddress() {
  // add address to the list
  const control = <FormArray>this.myForm.controls['addresses'];
  control.push(this.initAddress());
}

removeAddress(i: number) {
  // remove address from the list
  const control = <FormArray>this.myForm.controls['addresses'];
  control.removeAt(i);
}

HTML

<!-- list of addresses -->
<div formArrayName="addresses">
  <div *ngFor="let address of myForm.controls.addresses.controls; let i=index">
    <!-- address header, show remove button when more than one address available -->
    <div>
      <span>Address {{i + 1}}</span>
      <span *ngIf="myForm.controls.addresses.controls.length > 1" (click)="removeAddress(i)">
      </span>
    </div>

    <!-- Angular assigns array index as group name by default 0, 1, 2, ... -->
    <div [formGroupName]="i">
      <!--street-->
      <div>
        <label>street</label>
        <input type="text" formControlName="street">
        <!--display error message if street is not valid-->
        <small [hidden]="myForm.controls.addresses.controls[i].controls.street.valid">
          Street is required
        </small>
      </div>
      <!--postcode-->
      <div>
        <label>postcode</label>
        <input type="text" formControlName="postcode">
      </div>
    <div>
  </div>
</div>

Upvotes: 2

Related Questions