Reputation: 363
Can we split a reactive form into multiple components?.
For instance, The buttons (SAVE, UPDATE, and DELETE) in the parent component and the form is placed in child component.
How one can achieve this?
are there any examples?
Thanks
Upvotes: 1
Views: 4690
Reputation: 235
I hope that you need to trigger the form events like submit
, reset
etc.. from the parent component.
Steps to achieve
display:none
in the child component.label for
you can bind the click event to those buttons from the parent component.example
child component
<form (submit)="_onSubmit($event)"> <button type="submit" id="submitBTN_id"> submit</button> </form>
parent component
<a> <label for="submitBTN_id"> Submit </label> </a>
Upvotes: 0
Reputation: 29325
when I've done this in the past, I will generally use a service to manage and distribute the form.
@Injectable()
export class MyFormService {
private _form: FormGroup;
constructor(private fb: FormBuilder) {
this.buildForm()
}
private buildForm() {
this._form = this.fb.group({
// whatever form specifics in here
})
}
get form() {
return this._form
}
// whatever of these you need
get childForm() {
return this.form.get('childFormKey')
}
// any other appropriate methods or observables exposed
}
parent component:
@Component({
.. component stuff ..
providers: [MyFormService] // might as well provide it here to keep it private
})
export class ParentComponent {
constructor(private formService: MyFormService) {
}
onSubmit() {
const value = this.formService.form.value;
// do the submit
}
// any methods you need
}
child
export class ChildComponent {
childForm: FormGroup
constructor(private formService: MyFormService) {
this.childForm = this.formService.childForm;
// do whatever else you need for this component
}
}
this method is most applicable to big complex forms that you want to break into smaller less complex forms, but things that won't be highly reusable (though you can still get good reusability out with this method if you set things up correctly). It's also great for situations where you want the same overall form, but different templates.
Things that are more "widget" like, and you intend to use in many forms, will probably want a different method.
Upvotes: 2
Reputation: 1177
You can split a reactive form into multiple sub-forms but you need to make use of the ControlValueAccessor and its friend and you need to nest it in a parent form from where you submit it. And of course, you need to add some additional boilerplate in order to have all the validation in place and everything else that reactive forms require.
You can have as many sub-forms as you want this way.
Below you can see an example of how you can do it in the Angular way:
Child Component
import { Component, OnInit, forwardRef } from '@angular/core';
import {
NG_VALUE_ACCESSOR,
NG_VALIDATORS,
ControlValueAccessor,
Validator,
FormGroup,
FormBuilder,
AbstractControl,
ValidationErrors,
Validators
} from '@angular/forms';
@Component({
...
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => ChildComponent),
multi: true
},
{
provide: NG_VALIDATORS,
useExisting: forwardRef(() => ChildComponent),
multi: true
}
]
})
export class ChildComponent implements OnInit, ControlValueAccessor, Validator {
public childForm: FormGroup;
public onTouched: () => void = () => {};
constructor(private fb: FormBuilder) {}
ngOnInit(): void {
this.childForm = this.fb.group({
...
});
}
public writeValue(val: any): void {
return val && this.childForm.setValue(val, { emitEvent: false });
}
public registerOnChange(fn: any): void {
this.childForm.valueChanges.subscribe(fn);
}
public registerOnTouched(fn: any): void {
this.onTouched = fn;
}
public setDisabledState?(isDisabled: boolean): void {
isDisabled ? this.childForm.disable() : this.childForm.enable();
}
public validate(control: AbstractControl): ValidationErrors | null {
return this.childForm.valid
? null
: { invalidForm: { valid: false, message: 'Form is invalid' } };
}
}
Child Component template:
<ng-container [formGroup]="childForm">
.....
</ng-container>
Parent Component ts
import { Component, OnInit } from '@angular/core';
import { FormGroup, FormBuilder } from '@angular/forms';
@Component({
...
})
export class StandardSetupFormComponent implements OnInit {
parentForm: FormGroup;
constructor(private fb: FormBuilder) {}
ngOnInit(): void {
this.parentForm = this.fb.group({
child: null,
...
});
}
public onSubmit() {
...
}
}
Parent component template:
<form class="form" [formGroup]="parentForm" (ngSubmit)="onSubmit()">
<app-child formControlName="child"></app-child>
<button[disabled]="parentForm.invalid">Submit</button>
</form>
As you can see in the parent component you need to treat the child form as a form control.
There are other ways that you can do it. By using inputs for example and even if it looks simpler it's not the correct way.
I've read a great article about what I've shown you above a while ago, but I can't find it yet. I'll update the answer if I do.
Here is the article that will give you a better and deeper understanding of nesting reactive forms.
Upvotes: 4