0

I've got:

  • main component (also main formGoup) - register-form
  • child component (also child formGoup) - row

As you can see my main form (without child) in disabled and you can enable it by clicking edit button. I want also child component to be like that. So the whole form is disabled and on clicking the edit button it's enabled (you can type, chose etc). How do I do it? I've tried do something like that:

  constructor(private formBuilder: FormBuilder) {
    this.registerForm = this.formBuilder.group({
      username: [{value: '', disabled: this.disableForm}],
      email: [{value: '', disabled: this.disableForm}],
      password: [{value: '', disabled: this.disableForm}],
      confirmpassword: [{value: '', disabled: this.disableForm}],
      dropdown: [{value: '', disabled: this.disableForm}],
      checkbox1: [{value: '', disabled: this.disableForm}],
      rowForm: this.formBuilder.group(
        {
          input1: [''],
          input2: [''],
          input3: [''],
          checkbox2: [''] 
        }
      )
    });
  }

But it's not working at all.

My register-form (main component, main formgroup) html file:

<div class="main-wrapper" fxLayout="row" fxLayoutAlign="center center">
    <mat-card class="box">
        <mat-card-header class="header">
            <mat-card-title>Register</mat-card-title>
        </mat-card-header>

        <form [formGroup]="registerForm" (ngSubmit)="onSubmit()">

            <mat-checkbox class="example-margin" [formControl]="checkbox1">Show hidden option!</mat-checkbox>

            <!-- Username -->
            <mat-card-content>
                <mat-form-field class="example-full-width">
                    <input matInput placeholder="Username" name="username" minlength="5"
                        formControlName="username" required>
                </mat-form-field>
                <mat-error *ngIf="username.touched && !username.valid">
                    <div *ngIf="username.errors?.required">Username is required.</div>
                    <div *ngIf="username.errors?.minlength">Username should be minimum 5 characters.</div>
                </mat-error>

                <!-- Email -->
                <mat-form-field class="example-full-width">
                    <input matInput placeholder="Email"  name="email" minlength="5" [pattern]="emailPattern"
                        formControlName="email" required>
                </mat-form-field>
                <mat-error *ngIf="email.touched && !email.valid">
                    <div *ngIf="email.errors?.required">Email is required.</div>
                    <div *ngIf="email.errors?.pattern">Email is not valid.</div>
                </mat-error>

                <!-- Password -->
                <mat-form-field class="example-full-width">
                    <input matInput placeholder="Password"  name="password" minlength="5"
                        formControlName="password" required>
                </mat-form-field>
                <mat-error *ngIf="password.touched && !password.valid">
                    <div *ngIf="password.errors?.required">Password is required.</div>
                    <div *ngIf="password.errors?.minlength">Password should be minimum 5 characters.</div>
                </mat-error>

                <!-- Repeat Password -->
                <mat-form-field class="example-full-width">
                    <input matInput placeholder="Confirm Password" name="confirmpassword" formControlName="confirmpassword"
                        required>
                </mat-form-field>
                <mat-error *ngIf="confirmpassword.touched && confirmpassword.value != password.value"> Passwords does not match
                </mat-error>


            </mat-card-content> 

            <app-row [rowForm]="registerForm.controls.rowForm"></app-row>

            <button mat-button color="primary" class="btn-block" *ngIf="isEditable; else edit" type="submit"
                [disabled]="!registerForm.valid"> Register
            </button>

            <ng-template #edit>
                <button mat-button color="primary" class="btn-block" type="button" (click)="enableEdit()">
                    Edit
                </button>
            </ng-template>

        </form>
    </mat-card>

My register-form (main component, main formgroup) ts file:

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

@Component({
  selector: 'app-register-form',
  templateUrl: './register-form.component.html',
  styleUrls: ['./register-form.component.css']
})
export class RegisterFormComponent implements OnInit {

  emailPattern = '^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}$'; 
  registerForm;
  disableForm = true;
  isEditable;

  constructor(private formBuilder: FormBuilder) {
    this.registerForm = this.formBuilder.group({
      username: [{value: '', disabled: this.disableForm}],
      email: [{value: '', disabled: this.disableForm}],
      password: [{value: '', disabled: this.disableForm}],
      confirmpassword: [{value: '', disabled: this.disableForm}],
      checkbox1: [{value: '', disabled: this.disableForm}],
      rowForm: this.formBuilder.group(
        {
          input1: [{value: '', disabled: this.disableForm}],
          input2: [{value: '', disabled: this.disableForm}],
          input3: [{value: '', disabled: this.disableForm}],
          checkbox2: [{value: '', disabled: this.disableForm}] 
        }
      )
    });
  }


  ngOnInit(): void {
  }

  enableEdit() {
    this.disableForm = !this.disableForm;
    this.registerForm.enable();
    this.isEditable = true;
  }


  get username() {
    return this.registerForm.get('username');
  }

  get email() {
    return this.registerForm.get('email');
  }

  get password() {
    return this.registerForm.get('password');
  }

  get confirmpassword() {
    return this.registerForm.get('confirmpassword');
  }



  get checkbox1() {
    return this.registerForm.get('checkbox1');
  }


  onSubmit() {
    console.log(this.registerForm.value);
    this.registerForm.disable();
    this.isEditable = false;
  }
}

My row (child component, child formgroup) html file:

<form [formGroup]="rowForm">
  <div formArrayName="rows">
    <div *ngFor="let rows of getControls(); let i=index"  [formGroupName]="i">

      <mat-checkbox class="example-margin" formControlName="checkbox2"  name="checkbox2" (click)="toggleInput()">Show hidden option!</mat-checkbox>

      <mat-form-field class="example-full-width">
        <input matInput placeholder="Input1" formControlName="input1"  name="input1" required>
      </mat-form-field>

      <mat-form-field class="example-full-width">
        <input matInput placeholder="Input2" formControlName="input2"  name="input2" required>
      </mat-form-field>

      <mat-form-field *ngIf="showInput"  class="example-full-width">
        <input matInput placeholder="Input3" formControlName="input3"  name="input3" required>
      </mat-form-field>

      <button mat-button color="primary" *ngIf="rowForm.controls.rows.controls.length > 1" (click)="deleteRow(i)" class="btn btn-danger">Delete Button</button>
    </div>
  </div>
  
  <button mat-button color="primary" (click)="addNewRow()" class="btn btn-primary">Add new Row</button><br>

</form>

My row (child component, child formgroup) ts file:

import {Component, Input, OnInit} from '@angular/core';
import { FormBuilder, FormGroup, FormControl, Validators, FormArray} from '@angular/forms';

@Component({
  selector: 'app-row',
  templateUrl: './row.component.html',
  styleUrls: ['./row.component.css']
})
export class RowComponent implements OnInit {

  @Input() rowForm: FormGroup;
  showInput = false;


  constructor(private formBuilder: FormBuilder) {}


  ngOnInit() {
    this.rowForm = this.formBuilder.group({
      rows: this.formBuilder.array([this.createRow()])
    });
  }


  createRow(): FormGroup {
    return this.formBuilder.group({
      input1: ['', Validators.required],
      input2: ['', Validators.required],
      input3: ['', Validators.required],
      checkbox2: ['', Validators.required],
    });
  }

  toggleInput(index: number) {
    this.showInput = !this.showInput;
  }

  get formArray() {
    return this.rowForm.get('rows') as FormArray;
  }

  // Adding row function
  addNewRow() {
    this.formArray.push(this.createRow());
  }

  deleteRow(index: number) {
    this.formArray.removeAt(index);
  }

  getControls() {
    return (this.rowForm.get('rows') as FormArray).controls;
  }

}
JustABeginner
  • 67
  • 1
  • 9

1 Answers1

1

It's a bit old, but I like disabled a FormControl using a directive

Else you can create a function that Disable/Enable a formGroup and his children

  enable(form: FormGroup, enable: boolean) {
    Object.keys(form.controls).forEach(key => {
      const control = form.get(key);
      if (control) {
        if ((control as any).controls)
          this.enable(control as FormGroup, enable);
        else {
          if (enable) control.enable();
          else control.disable();
        }
      }
    });
  }

Using a directive is add to each input the directive and use a variable, e.g.

<input matInput [enableControl]="toogle" placeholder="Ex. Pizza" formControlName="prop1">

Each time you change the directive the input is disabled/enabled

The function is in your .ts, in your button "edit" simply

<button (click)="enable(registerForm,true)">Edit</button>

But there're some mistakes in your code. There're severals aproach to manage a nested Forms.

1.- The simpler way is create the Form in parent and pass the "formGroup" to the child. but you're making in an incorrect way. You need change when you create the registerForm

this.registerForm = this.formBuilder.group({
  username: [{value: '', disabled: this.disableForm}],
  email: [{value: '', disabled: this.disableForm}],
  password: [{value: '', disabled: this.disableForm}],
  confirmpassword: [{value: '', disabled: this.disableForm}],
  checkbox1: [{value: '', disabled: this.disableForm}],
  rowForm: this.formBuilder.group({
    rows:this.formBuilder.array([this.createRow(true)])
  })
});

  createRow(disabled:boolean=false): FormGroup {
    return this.formBuilder.group({
      input1: [{value:'',disabled:disabled}, Validators.required],
      input2: [{value:'',disabled:disabled}, Validators.required],
      input3: [{value:'',disabled:disabled}, Validators.required],
      checkbox2: [{value:'',disabled:disabled}, Validators.requiredTrue],
    });
  }

See that you're duplicate code to create the element in the formArray -you has another similar in your children- I changed a few the function to create the controls disabled.

2.-Create a FromGroup empty and add the formGroup in child. For this, we create the formgroup "rowForm" as a FromGroup empty

this.registerForm = this.formBuilder.group({
  username: [{value: '', disabled: this.disableForm}],
  ....
  rowForm: this.formBuilder.group({})
});

And in child we "change" this formGroup in ngOnInit. I enclosed in a setTimeout() to avoid the error "ExpressionChangedAfterItHasBeenCheckedError"

  ngOnInit() {
    setTimeout(()=>{
      this.rowForm.addControl("rows",this.formBuilder.array([this.createRow(true)]))

    })
  }

With this approach you needn't duplicate code

In this way you can use the function enable that I wrote in the code.

In this stackblitz there are the first option (create the whole form in parent)

In this stackbliz is the second one (create the rowForm in child and add to parent)

(*)To avoid duplicate code you can use ViewChild to get the children and use the function "createRow" of the children

Eliseo
  • 50,109
  • 4
  • 29
  • 67