Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
212 views
in Technique[技术] by (71.8m points)

Angular Reactive Forms, way to bind data object with every object of FormArray

I am building reactive form like the pic shown below-

As shown in pic every file (referred as attachment in code) have multiple agendas in them. Agendas could be added, updated and deleted

enter image description here

buildForms() {
this.agendaForms = this.formBuilder.group({
  attachements: this.formBuilder.array([

  ])
});

Form data is fetched from webservice and form model is created this way-

data => {
    this.submission = data;

    // New blank agenda for all attachements
    if (
      !isNullOrUndefined(this.submission) &&
      !isNullOrUndefined(this.submission.attachments)
    ) {
      this.submission.attachments.forEach(attachment => {
        const agenda = new NLAgenda();
        agenda.dwgNo = attachment.filename;
        agenda.dWGNo = attachment.filename;
        this.submission.agendas.push(agenda);

        if (!isNullOrUndefined(attachment)) {
          attachment.agendas = this.getAgendasForAttachment(attachment);

          if (!isNullOrUndefined(attachment.agendas)) {
            this.attachmentFormArray = this.agendaForms.controls
              .attachements as FormArray;
            this.attachmentFormArray.push(
              this.createAttachmentAgendasControl(attachment.agendas)
            );
          }
        }
      });
    }
  }

Templates look something like this

<form [formGroup]="agendaForms">
    <div formArrayName="attachements">
      <div *ngFor="let attachmentFormGroup of attachmentFormArray.controls;
          let attachmentId = index">

        <!-- Attachment header-->
        <div>
          <!-- File Name-->
          <div>
            {{ submission.attachments[attachmentId].filename }}
          </div>

          <!-- Action Buttons-->
          <div>
            <input type="button" value="Link To"/>
          </div>
        </div>

        <!-- Agendas -->
        <div formGroupName="{{ attachmentId }}">
          <div formArrayName="agendas">
            <div *ngFor="let agendaFormGroup of attachmentFormGroup.controls.agendas.controls;
                let agendaId = index">
              <div formGroupName="{{ agendaId }}" >
                <mat-form-field>
                  <input
                    type="text"
                    id="project"
                    placeholder="Sheet No."
                    formControlName="sheetNumber"
                    matInput
                  />
                  <!-- <mat-error>{{ getErrorMessage(f.project) }}</mat-error> -->
                </mat-form-field>
                <mat-form-field>
                  <input
                    type="text"
                    id="project"
                    placeholder="Title"
                    formControlName="title"
                    matInput
                  />
                </mat-form-field>
                <mat-form-field>
                  <input
                    type="text"
                    id="project"
                    placeholder="Description"
                    formControlName="description"
                    matInput
                  />
                </mat-form-field>

                <div>
                  <a
                    matTooltip="Add Agenda"
                    aria-label="Add Agenda"
                    (click)="
                      createOrUpdateAgenda(
                        submission.attachments[attachmentId].agendas[agendaId],
                        attachmentId,
                        agendaId
                      )">
                    <i class="fa fa-check"></i>
                  </a>
                  <a
                    *ngIf="submission.attachments[attachmentId].agendas[agendaId].created != null && submission.attachments[attachmentId].agendas[agendaId].created != undefined"
                    matTooltip="Delete Agenda"
                    aria-label="Delete Agenda"
                    (click)="deleteAgenda(submission.attachments[attachmentId].agendas[agendaId])">
                    <i class="fa fa-remove"></i>
                  </a>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </form>

Problem: Whenever agendas are added or updated I refetch submission object data from server to show updated state of files and agendas, submission object changes and gives errors in console like "Cannot read property 'agendas' of undefined" since in new submission object position of old objects is changed due to new addition or deletion

I believe I need to build template only using one array (FormArray) and not two arrays (Form array and Submission Object), else if one changes till other changes console will throw error. But how to use only FormArray, I have some data in Submission Object? Is there a way to bind submission object with FormArray?

I tired https://github.com/angular/angular/issues/13845 but didn't succeed as I am using form build and didn't know how to do this trick with form builder

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

It's more easy if you first think about the data you get. I think you get some like

[{filename:..,agenda:[
                     {sheetNumber:..,title:...,description...},
                     {sheetNumber:..,title:...,description...}
                          ...
                     ]
  },
  {filename:..,agenda:[
                     {sheetNumber:..,title:...,description...},
                     {sheetNumber:..,title:...,description...}
                          ...
                     ]
  }
  ....
]

You see that you has an FormArray, (a series of FormGroup) that has two properties, "filename" and "agenda" (Agenda is a new FormArray)

declare a variable as formArray

  form: FormArray

And create two functions.

createItemData(data):FormGroup
  {
    data=data|| {} as dataI;
    return new FormGroup(
      {
         filename:new FormControl(data.filename),
         agenda:new FormArray(data.agenda && data.agenda.length?
               data.agenda.map(agenda=>this.createAgendaSheet(agenda)):
               [])
      }
    )

  }
  createAgendaSheet(data):FormGroup{
    data=data|| {} as agendaI;
    return new FormGroup(
      {
        sheetNumber:new FormControl(data.sheetNumber),
        title:new FormControl(data.title),
        description:new FormControl(data.description),

      }
    )
  }

See how this functions create an element of the array. I use the next two interface to help me to create the form

export interface agendaI {
  sheetNumber: number,
  title: string,
  desription: string
}
export interface dataI {
  filename: string,
  agenda: agendaI[]
}

Well, be carefull when create the form:

<button (click)="addAgenda()">Add agenda</button>
<form *ngIf="form" [formGroup]="form">
    <!--form is a FormArray, so form.controls will be formGroup-->
    <div *ngFor="let control of form.controls;let i=index">
       <!--in this form group...-->
        <div [formGroup]="control">
            <!--we have a fromControl "filename"-->
            <input formControlName="filename"/><button (click)="add(i)">add</button>
      <!--and a FormArray "agenda"--->
      <div formArrayName="agenda">
        <div *ngFor="let c of control.get('agenda').controls;let j=index" >
          <div [formGroupName]="j">
            <input formControlName="sheetNumber">
            <input formControlName="title">
            <input formControlName="description">
            <button (click)="delete(i,j)">delete</button>
          </div>
        </div>
      </div>
    </div>
  </div>
</form>

well, the last step is create the function add, delete and addAgenda

addAgenda()
  {
    this.form.push(this.createItemData(null))
  }
  add(i)
  {
    (this.form.at(i).get('agenda') as FormArray).push(this.createAgendaSheet(null))
  }
  delete(i,j)
  {
    (this.form.at(i).get('agenda') as FormArray).removeAt(j)
  }

And, in ngOnInit create the form (I used a constant "data")

 ngOnInit() {
    this.form=new FormArray(data.map(d=>this.createItemData(d)));
  }

You can see the "ugly" result in stackblitz


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...