Stefan Falk
Stefan Falk

Reputation: 25397

How to dynamically add FormArray items and bind controls and models correctly?

I am having a calendar which is essentially represents a huge form. At the moment there is not a lot to see but what for now it gets a list of dates from which I start to build up a form:

this.dates.forEach(d => {
  group[d] = this._formBuilder.group({
    offers: this._formBuilder.array([])
  });
});

So, what I actually want is this: A map from date to a list of items

{
  '2018-01-01': [{id: 1}, {id: 2}],
  '2018-01-02': [{id: 5}, {id: 9}]
}

However, following this tutorial it seems I am currently ending up with something like:

{
  '2018-01-01': {offers: [{id: 1}, {id: 2}]},
  '2018-01-02': {offers: [{id: 5}, {id: 9}]}
}

which is good enough for me - I just want this to work.

I am passing down the offers form group in the parent component:

<form [formGroup]="form" *ngIf="form != null">  
  <div fxLayout="row" >  
    <app-calendar-day *ngFor="let date of dates$ | async" 
                      [formGroup]="form.get(date)">
    </app-calendar-day>  
  </div>  
</form>

This is working so far.

My problems start at the final step where I want to add offers/items to the FormArray.

<div [formGroup]="formGroup">
  <mat-card *ngFor="let offer of formGroup.get('offers').controls; let idx = index" style="margin: 4px; padding: 10px;">
    <app-offer-item [formGroupName]="idx">
    </app-offer-item>
  </mat-card>
</div>

The calendar allows to add items/offers and I want formGroup.get('offers') (the FormArray) to be built up dynamically s.t. I can post the entire thing at the end ot the day.

However, I do not know how to bind the offer/item correctly.

The app-offer-item is built on AbstractControlComponent which delivers ControlValueAccessor and Validator interfaces as well as some default implementations. The code for OfferItemComponent aka app-offer-item is below. Since it encapsulates an object, I am quite certain that this element should represent a FormGroup but I am not able to wire the pieces together correctly as it seems.

The code above is giving me

ERROR Error: Cannot find control with unspecified name attribute

This seems to happen right after I add an item/offer to the FormArray:

onAddOffer(offer) {
  const formArray = <FormArray> this.formGroup.get('offers');
  formArray.push(this._formBuilder.group(offer));
  // ..
}

I expected writeValue() in OfferItemComponent to be called somehow but I guess that's not how it works.

I think I have two options here:

  1. Find a way to make this work after all
  2. Pass down the additional FormGroup to OfferItemComponent and try to make it work like that

Any help is very much appreciated!


OfferItemComponent

@Component({
  selector: 'app-offer-item',
  styles: [`
  `],
  template: `
    <div>      
      <div [innerHTML]="getItemHtml()"></div>

      <div>
        <span style="padding-right: 4px;">€</span>
        <mat-form-field style="text-align: right; width: 55px; margin-bottom: -1.25em;">
          <input matInput autocomplete="off" (focusout)="formatPrice($event.target)"/>
        </mat-form-field>        
      </div>

    </div>
  `,
  providers: [
    DecimalPipe,
    ValueAccessorProvider(OfferItemComponent),
    ValidatorsProvider(OfferItemComponent)
  ]
})
export class OfferItemComponent extends AbstractControlComponent {
  @Input()
  items;

  offer: OfferModel;

  constructor(
    @Inject(LOCALE_ID) public locale: string,
    private _decimalPipe: DecimalPipe
  ) {
    super();
  }

  writeValue(offer: OfferModel): void {
    this.offer = offer;
    this._onChangeCallback(this.offer);
  }

  validate(c: AbstractControl): ValidationErrors | any {
    // ..
  }

  getItemHtml() {
    // ..
  }

  formatPrice(element) {
    // ..
  }

}

Upvotes: 0

Views: 681

Answers (2)

JBoothUA
JBoothUA

Reputation: 3149

I believe that all of your form control elements within a [formGroup] need to have the "name" and the "formControlName" attribute set as well.

[attr.data-name]="name_of_field" [formControlName]="name_of_field"

Upvotes: 1

Sunil
Sunil

Reputation: 11243

The missing element in your code is formArrayName

<div formArrayName="offers">
  <mat-card *ngFor="let offer of formGroup.get('offers').controls; let idx = index" style="margin: 4px; padding: 10px;">
    <app-offer-item [formGroupName]="idx">
    </app-offer-item>
  </mat-card>
</div>

Upvotes: 0

Related Questions