At the company I’m working for, we’re developing a large scale application with multiple forms, that the user needs to fill in in order to register for our program. When all questions have been answered, then the user reaches a section that sums up all their answers, highlights invalid answers and gives the user the chance to revisit any of the preceding form steps and revise their answers. This logic will be repeated across a range of top-level sections, each having multiple steps/pages and a summary page.
To accomplish this, we have created a component for each separate form step (they are categories like “Personal Details” or “Qualifications” etc.) along with their respective routes and a component for the Summary Page.
In order to keep it as DRY as possible, we started creating a “master” service which holds the information for all the different form steps (values, validity etc.).
import { Injectable } from '@angular/core';
import { Validators } from '@angular/forms';
import { ValidationService } from '../components/validation/index';
@Injectable()
export class FormControlsService {
static getFormControls() {
return [
{
name: 'personalDetailsForm$',
groups: {
name$: [
{
name: 'firstname$',
validations: [
Validators.required,
Validators.minLength(2)
]
},
{
name: 'lastname$',
validations: [
Validators.required,
Validators.minLength(2)
]
}
],
gender$: [
{
name: 'gender$',
validations: [
Validators.required
]
}
],
address$: [
{
name: 'streetaddress$',
validations: [
Validators.required
]
},
{
name: 'city$',
validations: [
Validators.required
]
},
{
name: 'state$',
validations: [
Validators.required
]
},
{
name: 'zip$',
validations: [
Validators.required
]
},
{
name: 'country$',
validations: [
Validators.required
]
}
],
phone$: [
{
name: 'phone$',
validations: [
Validators.required
]
},
{
name: 'countrycode$',
validations: [
Validators.required
]
}
],
}
},
{
name: 'parentForm$',
groups: {
all: [
{
name: 'parentName$',
validations: [
Validators.required
]
},
{
name: 'parentEmail$',
validations: [
ValidationService.emailValidator
]
},
{
name: 'parentOccupation$'
},
{
name: 'parentTelephone$'
}
]
}
},
{
name: 'responsibilitiesForm$',
groups: {
all: [
{
name: 'hasDrivingLicense$',
validations: [
Validators.required,
]
},
{
name: 'drivingMonth$',
validations: [
ValidationService.monthValidator
]
},
{
name: 'drivingYear$',
validations: [
ValidationService.yearValidator
]
},
{
name: 'driveTimesPerWeek$',
validations: [
Validators.required
]
},
]
}
}
];
}
}
That service is being used by all the components in order to set up the HTML form bindings for each, by accessing the corresponding object key and creating nested form groups, as well as by the Summary page, whose presentation layer is only 1way bound (Model -> View).
export class FormManagerService {
mainForm: FormGroup;
constructor(private fb: FormBuilder) {
}
setupFormControls() {
let allForms = {};
this.forms = FormControlsService.getFormControls();
for (let form of this.forms) {
let resultingForm = {};
Object.keys(form['groups']).forEach(group => {
let formGroup = {};
for (let field of form['groups'][group]) {
formGroup[field.name] = ['', this.getFieldValidators(field)];
}
resultingForm[group] = this.fb.group(formGroup);
});
allForms[form.name] = this.fb.group(resultingForm);
}
this.mainForm = this.fb.group(allForms);
}
getFieldValidators(field): Validators[] {
let result = [];
for (let validation of field.validations) {
result.push(validation);
}
return (result.length > 0) ? [Validators.compose(result)] : [];
}
}
After, we started using the following syntax in the components in order to reach the form controls specified in the master form service:
personalDetailsForm$: AbstractControl;
streetaddress$: AbstractControl;
constructor(private fm: FormManagerService) {
this.personalDetailsForm$ = this.fm.mainForm.controls['personalDetailsForm$'];
this.streetaddress$ = this.personalDetailsForm$['controls']['address$']['controls']['streetaddress$'];
}
which seems like a code smell in our inexperienced eyes. We have strong concerns how an application like this will scale, given the amount of sections we'll have in the end.
We’ve been discussing different solutions but we can’t come up with one that leverages Angular’s form engine, allows us to keep our validation hierarchy intact and is also simple.
Is there a better way to achieve what we’re trying to do?
See Question&Answers more detail:
os