Angular 2 simulated nested forms

What I have:

I am creating an ionic 2 application and have created an angular 2 core component containing

  • Entry field

  • Label for displaying input headers

  • Shortcut to display any validation errors

I will refer to this as my input component

I have a page component with a form on it and currently have text inputs. 1 regular input (password) and 1 input wrapped in my input component (username).

this is the relevant part of my page component

ngOnInit() { this.loginForm = this.formBuilder.group({ username: ['', Validators.required], password: ['', Validators.required] }); } 

This is a page component template.

 <form [formGroup]="loginForm" (ngSubmit)="onSubmit()"> <!-- My input component --> <aw-input-text id="username" name="Username" [formInput]="loginForm.controls.username"></aw-input-text> <!-- A standard input control --> <ion-item [class.error]="loginForm.controls.password.errors"> <ion-label floating>Password</ion-label> <ion-input type="text" value="" name="password" formControlName="password"></ion-input> <p *ngIf="loginForm.controls.password.errors">This field is required!</p> </ion-item> <button type="submit" class="custom-button" [disabled]="!loginForm.valid" block>Login</button> </form> 

This is the template for my input component.

 <!-- Component template --> <form [formGroup]="formGroup"> <ion-item> <ion-label floating>{{inputName}}</ion-label> <ion-input type="text" formControlName="inputValue"></ion-input> <p *ngIf="!formGroup.controls.inputValue.valid">This field is required!</p> </ion-item> </form> 

and this is the input component

 import {Component, Input} from '@angular/core'; import {FormBuilder} from '@angular/forms'; @Component({ selector: 'aw-input-text', templateUrl: 'build/shared/aw-input-text/aw-input-text.html' }) export class AwInputText { @Input('formInput') public formInput; @Input('name') public inputName; public formGroup; constructor(private formBuilder: FormBuilder) { } ngOnInit() { this.formGroup = this.formBuilder.group({ inputValue: this.formInput }); } } 

The component displays correctly.

Problem:

Entrance inside the component does not update the actual state of the form in which it is located.

When I fill in the username and then the password, the form becomes valid.

When I fill out the password, the form username remains invalid

Thus, the form can see the actual state of the input component, but only that the input component that changes the actual state does not start the updated form.

Possible Solution 1

As described in this article and plunk

https://scotch.io/tutorials/how-to-build-nested-model-driven-forms-in-angular-2

https://plnkr.co/edit/clTbNP7MHBbBbrUp20vr?p=preview

I could modify my page component to create a form group containing a nested form group for each form control that I want to use inside my input component

 ngOnInit() { this.loginForm = this.formBuilder.group({ username: this.formBuilder.group({ username: ['', Validators.required], }), password: ['', Validators.required], }); } 

This solution is suitable for the scenario in the article where they add an array of input controls, but in my case, I find it seems hacky

Possible Solution 2

Another hacker solution that I examined uses the @output directive from my input component to trigger an event on the page component, which updates the form whenever the input component is updated.

Input Component Update

 this.formGroup.controls.inputValue.valueChanges.subscribe(value => { this.formUpdated.emit({ value: value }) }); 

Refresh Page Component

 public onUpdated(value){ this.loginForm.updateValueAndValidity(); } 

and update the page component template

 <aw-input-text id="username" name="Username" (updated)="onUpdated($event)" [formInput]="loginForm.controls.username"></aw-input-text> 

This gives me the desired functionality, but I think it also seems a bit hacky having an event handler on every page of the form to make the input component work.

Question

Is there a way to get my component to update the correct state of the form it is in (bearing in mind that I would like to reuse this component several times in each form) without resorting to the solution described above.

+7
javascript angular forms angular2-components angular2-forms
source share
3 answers

Thanks for the answers to the guys. As a result, we created two components: the user form component and the user input component.

We insert as many user input components as we need in the custom form component, and the custom form component uses @ContentChildren to identify and register all child components created by the user.

Thus, we do not need to submit the form to each input, and we do not have a mess of nested form groups for each input.

 // Each CustomInputText component exposes a FormControl and // a control definition which has additional info about the control @ContentChildren(CustomInputText, {descendants: true}) public customInputComponents: QueryList<CustomInputText>; private initialised; public ngAfterContentChecked() { // Only initialise the form group once if (!this.initialised) { this.initialised = true; this.customInputComponents.forEach((input)=>{ this.formGroup.addControl(input.controlDefinition.id, input.formControl); }); } } 
+4
source share

You can @ Embed your loginForm in a nested component, as "parentForm" allows. Then register the nested formGroup in parentForm on the init child component and write it to the destroy child component.

+3
source share

What I did in my case (nested dynamic form) somehow resembles Marcin's answer.

I pass the existing FormGroup as parentForm , and my Component view looks like this:

 <fieldset [formGroup]="parentForm"> <label *ngIf="label" [attr.for]="key">{{label}}</label> <input [id]="key" [type]="type" [formControlName]="key" /> </fieldset> 

It fits my needs. Hope this helps you too.

UPDATE: I created a library to speed up the creation of dynamic forms. You can take a look at how I use the technique from this answer: https://www.npmjs.com/package/dorf

+3
source share

All Articles