2

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.

3 Answers 3

10

The response before - in my opinion - it is a hackish response, which brings extra burden to your typescript code and bypasses the framework mechanisms. It is specially overkilling when you're using template-drive forms - which require no typescript code to control the form.

The solution? Use what the framework provides (inside your child @Component({})):

viewProviders: [{provide: ControlContainer, useExisting: NgForm}]

For a working example of this see this repository

Sign up to request clarification or add additional context in comments.

2 Comments

+ for the working solution! But would be nice to have some explanation of what it is and why it works.
For anyone having the same issue as me, I had a problem with my template-driven form where the parts controlled by child components were validating correctly but the overall form validity was not being updated according to the child components (e.g., some children might be invalid but the overall form was still being marked valid). This answer fixed it wonderfully.
0

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);
  }

1 Comment

I'm a little bit confuse, formControl is not used for reactive form? Or what is the use of formControl in a template driven form?
-1

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");
      }

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.