I'm trying to find a better approach to handle complex angular forms. The form is really big and I need to find an approach to reduce complexity.
Here is an example of form structure:
{
"fieldA" : ...,
"fieldB" : ...,
"fieldC" : ...,
"profile": {
"username": ...,
"email": ...,
"firstName": ...,
"lastName": ...,
...
},
"settings": {
"enableEmailNotification": ...,
"notificationsEmail": ..., // required when enableEmailNotification
...
},
...
}
There are cases when validators are changed on the fly, for example when enableEmailNotification=true
, component will add Required
validator to notificationsEmail
Here are researched options:
sample on github
This approach uses one form and one component.
Pros:
Cons:
sample on github
This approach sends formGroup
to inner components as @Input()
property.
Pros:
Cons:
sample on github
Based on a this article we can create custom ControlValueAccessor which will return an object for a part of a form.
Pros:
Cons:
Reactive forms are more scalable than template-driven forms. They provide direct access to the underlying form API, and use synchronous data flow between the view and the data model, which makes creating large-scale forms easier.
In Angular, a reactive form is a FormGroup that is made up of FormControls. The FormBuilder is the class that is used to create both FormGroups and FormControls.
The FormBuilder provides syntactic sugar that shortens creating instances of a FormControl , FormGroup , or FormArray . It reduces the amount of boilerplate needed to build complex forms.
My strategy for large complex forms is to have a wrapper component and sub components. each sub component has it's own form service and the wrapper has a master form service with the sub form services injected, consider this
@Component({
selector: 'form-wrapper',
template: `
<form [formGroup]="form" (submit)="save()">
<sub-form-a></sub-form-a>
<sub-form-b></sub-form-b>
<input type="submit" value="Submit Form">
</form>
`,
providers: [MasterFormService, FormAService, FormBService]
})
export class FormWrapper {
constructor(private formService: MasterFormService) { }
save() {
// whatever save actions here
}
}
@Component({ // form b compoent is basically the same
selector: 'sub-form-a',
template: `
... whatever template belongs to form a ..
`
})
export class FormAComponent {
form: FormGroup
constructor(private formService: FormAService) {
this.form = this.formService.form;
// form a specific actions
}
}
@Injectable()
export class MasterFormService {
form: FormGroup;
constructor(private fb: FormBuilder, formAService: FormAService, formBService: FormBService) {
this.form = this.fb.group({
groupA: this.formAService.form,
groupB: this.formBService.form,
});
}
}
@Injectable() // formB service is basically the same
export class FormAService {
form: FormGroup;
constructor(private fb: FormBuilder) {
this.form = this.fb.group({
.. whatever fields belong to form a ..
});
}
}
this method creates highly reusable sub forms and lets you modularize / isolate form logic and templates. I often find that sub forms typically belong in more than one place anyway, so it keeps my code very DRY. In your example in particular, you can easily reuse the settings form and profile form components elsewhere in your application. One or twice I've even nested this structure again for an extremely complex form.
The con is that the structure may appear complex but you get used to it quick.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With