1
votes

I am working on Angular 10. Its been 3 days that I have a problem with angular template-driven and reactive forms.

What I want to do: Create a complex form with many child components (in order to separate lot of code). Then on one button click I want to validate all the form and the child components. I want somehow to validate children components.

What I created: A template-driven form with 2-way bindings because I believe its easier and not needed for my situation.

I have managed to make the validation work only for the base form. I couldn't do it on the children components. I saw many posts about this issue and tried a lot but I am very confused.

My code :

mainForm.html

<div class="card m-3">
    
    <div class="card-body">
      <fieldset>
        <form name="form" (ngSubmit)="f.form.valid && onSubmit()" #f="ngForm" novalidate>
        
            <div class="form-row">
                <div class="form-group col">
                    <label>Title</label>
                    <select name="title" class="form-control" [(ngModel)]="model.title" #title="ngModel" [ngClass]="{ 'is-invalid': f.submitted && title.invalid }" required>
                        <option value=""></option>
                        <option value="Mr">Mr</option>
                        <option value="Mrs">Mrs</option>
                        <option value="Miss">Miss</option>
                        <option value="Ms">Ms</option>
                    </select>
                    <div *ngIf="f.submitted && title.invalid" class="invalid-feedback">
                        <div *ngIf="title.errors.required">Title is required</div>
                    </div>
                </div>
                <div class="form-group col-5">
                    <label>First Name</label>
                    <input type="text" name="firstName" class="form-control" [(ngModel)]="model.firstName" #firstName="ngModel" [ngClass]="{ 'is-invalid': f.submitted && firstName.invalid }" required>
                    <div *ngIf="f.submitted && firstName.invalid" class="invalid-feedback">
                        <div *ngIf="firstName.errors.required">First Name is required</div>
                    </div>
                </div>
                <div class="form-group col-5">
                    <label>Last Name</label>
                    <input type="text" name="lastName" class="form-control" [(ngModel)]="model.lastName" #lastName="ngModel" [ngClass]="{ 'is-invalid': f.submitted && lastName.invalid }" required>
                    <div *ngIf="f.submitted && lastName.invalid" class="invalid-feedback">
                        <div *ngIf="lastName.errors.required">Last Name is required</div>
                    </div>
                </div>
            </div>
            
            <div class="form-row">
                <div class="form-group col">
                    <label>Date of Birth</label>
                    <input type="date" name="dob" class="form-control" [(ngModel)]="model.dob" #dob="ngModel" [ngClass]="{ 'is-invalid': f.submitted && dob.invalid }" required pattern="^\d{4}\-(0[1-9]|1[012])\-(0[1-9]|[12][0-9]|3[01])$">
                    <div *ngIf="f.submitted && dob.invalid" class="invalid-feedback">
                        <div *ngIf="dob.errors.required">Date of Birth is required</div>
                        <div *ngIf="dob.errors.pattern">Date of Birth must be a valid date in the format YYYY-MM-DD</div>
                    </div>
                </div>             
            </div>

>  One child component for demonstration(there are more)--code continues below

              <app-extra-info  [(model)]="model"></app-extra-info> pass model in children
  
            <div class="text-center">
                <button class="btn btn-primary mr-1">Register</button>            
            </div>
        </form>
      </fieldset>
  </div>
</div>

childcomponent.html:

  <div class="form-group row">
    <label class="col-2 col-form-label">Gender</label>
    <div class="col-4">
      <select id="gender" name="gender" class="form-control"  required>
        <option>Male</option>
        <option>Female</option>
         <option>Not Specify</option>
         <option></option>
      </select>        
    </div>   
  </div>

  <div class="form-group row">
    <label>Pet name</label>
    <input type="text" name="petName" class="form-control" [(ngModel)]="model.petName" #petName="ngModel" [ngClass]="{ 'is-invalid': petName.invalid && petName.dirty}" required>
    <div *ngIf="petName.invalid" class="invalid-feedback">
        <div *ngIf="petName.errors.required">Pet Name is required</div>
    </div>
  </div>

childrencomponent.ts:

import { Component, Input, OnInit } from '@angular/core';

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

  @Input() model: any;
  
  constructor() { }

  ngOnInit(): void {
  }

}

parent.ts

import { Component, OnInit } from '@angular/core';

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

  model: any = {};

  constructor() {

   }

  ngOnInit() {
    
  }

  onSubmit() {
    alert(JSON.stringify(this.model, null, 4));
  }

}

The code works as it is. The problem is that when I press Register, anything in the children component wont get validated.(in this case Pet name and gender)

Demo picture: Form

Questions I have:

  1. Do I need to use template-driven or reactive forms? I said before that template-driven forms are easier and not needed for my situation but, I am not quite sure about this.

  2. Do I need to pass the form into the children or pass children to main form? Is there a way to implement validation to children components using Template driven form?

Please can someone enlighten me what is the correct thing to do, give me some feedback and any suggestion. Don't hesitate to ask me anything or if you want more details.

Thank you for your time.

2

2 Answers

0
votes

You can pass in a formControl:

 @Input() formControl: FormControl;

Then pass it like this

  <app-extra-info  [formControl]="f.get('petName')"></app-extra-info> pass model in children

then pass the form to the submit

<form name="form" (ngSubmit)="onSubmit(f.form)" #f="ngForm" novalidate>


onSubmit(form: FormGroup) {
    form.get('petName').updateValueAndValidity();

    alert( form.get('petName').valid);
  }
0
votes

I have managed to implement something that is working.

What I have done is to add @ViewChild(ChildComponent) child-Component: ChildComponent; in parent.ts.

Then in child component I have created a function that checks the children form and if there are any errors I return a Boolean variable. That function is used in parent. I am doing this for each children. Shortly, I am doing form validation in each child component and I am getting a Boolean variable in parent.

In parent I have something like :

if (!this.child-Component.validform) {
        console.log("child has missing inputs");
      }