0

I am creating an HTML form in Angular. I am running into an issue. I want to be able to display a duplicate of an HTML block with a new form control at the click of a button. Here is what thing look like now:

enter image description here

I would like the user to be able to click the button labeled Click me and have a duplicate of the HTML block display but with a different form control. Do you guys have any suggestions of how I can do that? Here is what I have so far.

import { Component, OnInit, Directive, ViewChild } from '@angular/core';
import { FormControl, FormGroupDirective, NgForm, Validators, FormBuilder, FormGroup } from '@angular/forms';

@Component({
  selector: 'app-questions',
  templateUrl: './questions.component.html',
  styleUrls: ['./questions.component.scss']
})

export class QuestionsComponent implements OnInit {
  jobForm: FormGroup = this.fb.group({
    companyName: this.fb.control('', [Validators.required ]),
    position: this.fb.control('', [Validators.required ]),
    description: this.fb.control('', [Validators.required ]),
    startDate: this.fb.control('', [Validators.required ]),
    endDate: this.fb.control('', [Validators.required ])
  });
  constructor(private readonly fb: FormBuilder) { }

  ngOnInit(): void {
  }  
  
  displayForm() {
    console.log(this.jobForm);
  }


}
<h3>Education:</h3>
    <form [formGroup]="jobForm">
      <mat-form-field >
        <mat-label>Company Name: </mat-label>
        <input matInput type="text" formControlName="companyName"/>
        <mat-error *ngIf="jobForm.controls.companyName.errors">Company name is required</mat-error>
      </mat-form-field>
      <mat-form-field >
        <mat-label>Position: </mat-label>
        <input matInput type="text" formControlName="position"/>
        <mat-error *ngIf="jobForm.controls.position.errors">Position is required</mat-error>
      </mat-form-field>
      <mat-form-field >
        <mat-label>Select start and end date:</mat-label>
        <mat-date-range-input [rangePicker]="picker">
          <input matStartDate placeholder="Start date" formControlName="startDate">
          <input matEndDate placeholder="End date" formControlName="endDate">
        </mat-date-range-input>
        <mat-datepicker-toggle matSuffix [for]="picker"></mat-datepicker-toggle>
        <mat-date-range-picker #picker></mat-date-range-picker>
      </mat-form-field>
      <mat-form-field >
        <mat-label>Description: </mat-label>
        <textarea matInput type="text" formControlName="description"></textarea>
        <mat-error *ngIf="jobForm.controls.description.errors">Job description is required</mat-error>
      </mat-form-field>
    </form>
    <button (click)="displayForm()">Click me</button>

After a user hits the click me button id like to generate a duplicate form so they can fill out the details.

Thanks

2 Answers 2

2

I believe what you're asking for is a way to dynamically add a form group to the page. If that is the case then the solution below should help.

You can use the *ngFor structural directive to iterate over a FormGroup array. The following adjustments will need to be made:

import { Component, OnInit, Directive, ViewChild } from '@angular/core';
import { FormControl, FormGroupDirective, NgForm, Validators, FormBuilder, FormGroup } from '@angular/forms';

@Component({
  selector: 'app-questions',
  templateUrl: './questions.component.html',
  styleUrls: ['./questions.component.scss']
})

export class QuestionsComponent implements OnInit {

  jobForms: FormGroup[] = []; // Declare an empty array

  constructor(private readonly fb: FormBuilder) { }

  ngOnInit(): void {
    this.addFormRow(); // Add an empty form group to the array
  }  
  
  //displayForm() {
  //  console.log(this.jobForm);
  //}

  // Add an additional row to the jobForms array - to be called from the template
  addFormRow() {
    this.jobForms.push(this.fb.group({
      companyName: this.fb.control('', [Validators.required ]),
      position: this.fb.control('', [Validators.required ]),
      description: this.fb.control('', [Validators.required ]),
      startDate: this.fb.control('', [Validators.required ]),
      endDate: this.fb.control('', [Validators.required ])
    }));
  }
}
<h3>Education:</h3>

    <form *ngFor="let formGroup of jobForms" 
          [formGroup]="formGroup">
      <mat-form-field >
        <mat-label>Company Name: </mat-label>
        <input matInput type="text" formControlName="companyName"/>
        <mat-error *ngIf="jobForm.controls.companyName.errors">Company name is required</mat-error>
      </mat-form-field>
      <mat-form-field >
        <mat-label>Position: </mat-label>
        <input matInput type="text" formControlName="position"/>
        <mat-error *ngIf="jobForm.controls.position.errors">Position is required</mat-error>
      </mat-form-field>
      <mat-form-field >
        <mat-label>Select start and end date:</mat-label>
        <mat-date-range-input [rangePicker]="picker">
          <input matStartDate placeholder="Start date" formControlName="startDate">
          <input matEndDate placeholder="End date" formControlName="endDate">
        </mat-date-range-input>
        <mat-datepicker-toggle matSuffix [for]="picker"></mat-datepicker-toggle>
        <mat-date-range-picker #picker></mat-date-range-picker>
      </mat-form-field>
      <mat-form-field >
        <mat-label>Description: </mat-label>
        <textarea matInput type="text" formControlName="description"></textarea>
        <mat-error *ngIf="jobForm.controls.description.errors">Job description is required</mat-error>
      </mat-form-field>
    </form>
    <!-- Call addFormRow() to add a FormGroup to the array -->
    <button (click)="addFormRow()">Click me</button>
Sign up to request clarification or add additional context in comments.

Comments

0

If you can use Lodash library it would be easy


//const newjobFormGroup = _.cloneDeep(jobForm) as FormGroup;

CopyJobForm(toCopyForm: FormGroup){
 //return _.cloneDeep(jobForm) as FormGroup;
 return _.cloneDeep(FormGroup) as FormGroup;
}

Or

this answer for a full deep clone

/**
 * Deep clones the given AbstractControl, preserving values, validators, async validators, and disabled status.
 * @param control AbstractControl
 * @returns AbstractControl
 */
export function cloneAbstractControl<T extends AbstractControl>(control: T): T {
  let newControl: T;

  if (control instanceof FormGroup) {
    const formGroup = new FormGroup({}, control.validator, control.asyncValidator);
    const controls = control.controls;

    Object.keys(controls).forEach(key => {
      formGroup.addControl(key, cloneAbstractControl(controls[key]));
    })

    newControl = formGroup as any;
  }
  else if (control instanceof FormArray) {
    const formArray = new FormArray([], control.validator, control.asyncValidator);

    control.controls.forEach(formControl => formArray.push(cloneAbstractControl(formControl)))

    newControl = formArray as any;
  }
  else if (control instanceof FormControl) {
    newControl = new FormControl(control.value, control.validator, control.asyncValidator) as any;
  }
  else {
    throw new Error('Error: unexpected control value');
  }

  if (control.disabled) newControl.disable({emitEvent: false});

  return newControl;
}

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.