April 1, 2019
Refactorable reactive forms in angular
Problem
When creating forms with Angular, many accesses take place in the template using strings. This makes it difficult to perform refactorings automatically. It would be so desirable:
Status quo
Also the FormBuilder does not manage to get the problems better under control. It is only a help for the creation of the data structure within the component class. No matter if the FormGroup is created with the own call of new FormGroup() or with the FormBuilder, in both cases only inline instances of the single FormControls end up in the group.
1 2 3 4 5 6 7 8 |
<mat-form-field class="example-full-width"> <textarea matInput placeholder="description" [formControl]="form.get('description')"></textarea> <mat-hint>Description is optional but should have at 10 Characters</mat-hint> <mat-error *ngIf="form.get('description').hasError('minlength')"> Your description is to short. It should be at least {{form.get('description').errors?.minlength.requiredLength}}, but you have only {{form.get('description').errors?.minlength.actualLength}} tough. </mat-error> </mat-form-field> |
1 2 3 4 5 6 7 8 |
ngOnInit() { this.form = new FormGroup({ name: new FormControl(this.todoItem.name, Validators.required), assignedTo: new FormControl(this.todoItem.assignedTo), description: new FormControl(this.todoItem.description, Validators.minLength(10)), due: new FormControl(this.todoItem.due), }); } |
Way to go
First and foremost, we are only concerned with the individual FormControl instances that we want to use in the template. In the first step I go here and drag the inline specified map of the FormControls from the constructor of the FormGroup into a new instance variable of the component class.
1 2 3 4 5 6 7 |
formModel = { name: new FormControl(null, Validators.required), assignedTo: new FormControl(null), description: new FormControl(null, Validators.minLength(10)), due: new FormControl(null), }; form: FormGroup = new FormGroup(this.formModel); |
The initialization of the individual FormControl is changed to zero. This is necessary because the data will not be available at that time. All inputs were set in the OnInit phase. From this point on it is safe to write the data to the group using this.form#patchValue .
1 2 3 |
ngOnInit() { this.form.patchValue(this.todoItem); } |
This change allows me to bind [FormControl] with the new variable formModel in the template. The IDE can support me with auto completion.
1 2 3 4 5 6 7 8 |
<mat-form-field class="example-full-width"> <textarea matInput placeholder="description" [formControl]="formModel.description"></textarea> <mat-hint>Description is optional but should have at 10 Characters</mat-hint> <mat-error *ngIf="formModel.description.hasError('minlength')"> Your description is to short. It should be at least {{formModel.description.errors?.minlength.requiredLength}}, but you have only {{formModel.description.errors?.minlength.actualLength}} tough. </mat-error> </mat-form-field> |
The only thing that’s missing is the possibility to let Typescript support me with future changes. In most cases we choose our form structure so that we can work directly with form.value. In type script this simply means that form.value corresponds to the type Partial<Todo> . Now we want to have a type in our hand that has the same keys, but whose values all have the type FormControl. This can be expressed in type script as follows: {[key in keyof Todo]?: FormControl} . So from now on we will use this type for our Map formModel. Now the IDE is able to support us with changes at our interfaces also in our forms. And that not only in our type script code but also in the rather fragile connection between the template and our component class.
1 2 3 4 5 6 |
formModel: {[key in keyof Todo]?: FormControl} = { name: new FormControl(null, Validators.required), assignedTo: new FormControl(null), description: new FormControl(null, Validators.minLength(10)), due: new FormControl(null), }; |
You can find the complete code on Github.
Also an interesting approach can be found here https://ruanbeukes.net/angular-typesafe-reactive-forms/