I would like to create a stronger type-safe link between an Angular template and a FormGroup. I thought of the following but I do not know how to express it in TypeScript properly.
I want to use an object to set the keys of the controls
object of a FormGroup (the first parameter of the FormControl
constructor), as well as the binding of the formControlName
in the template, starting from a model class.
My reasoning is that this will remove the "stringy" or so-called magic string API arising from using strings for the formControlName
attribute. This way, the single source of truth would be the model class. This would also allow me to easily refactor that class and have all other references update.
For example, I would have liked to be able to write something like this:
// model class:
class Partner {
id: string;
name: string;
}
// some static type SomeType = { id: "id", name: "name" } extends Partner's public properties
// but the values of those properties must equal the property name.
// component:
@Component({
template: `
<form [formGroup]="form" (ngSubmit)="onSubmit()" novalidate>
<!-- notice I'm not using a stringy API like formControlName="id" -->
<input type="text" [formControlName]="SomeType.id">
<input type="text" [formControlName]="SomeType.name">
</form>
`})
class CreatePartnerComponent {
form = new FormGroup({
SomeType.id: new FormControl(''),
SomeType.name: new FormControl('')
});
}
Thank you!
I seem to have found a (one) way.
class ModelClass {
id: string;
name: string;
}
// for the template I need a literal object that I can access by key
// and provide form control names as strings to bind the formControlName
// directive to, so I declare this inside the component class:
readonly fcName: { [key in keyof ModelClass]: key } = {
// the problem with this is you need to explicitly declare the properties here and
// keep this property up to date when you refactor ModelClass, but you still
// get an error if you forget to, so that's great.
// Also some IDEs can auto-fill these properties along with the values as well.
id: 'id',
name: 'name'
}
// and bind form controls like [formControlName]="fcName.id" and so on,
// in the template, this should be pretty obvious by now.
// and the form group you now declare like this, which
// enforces correct key names based on the model:
form = new FormGroup({
id: new FormControl(''),
name: new FormControl('')
} as { [key in keyof ModelClass]: FormControl });
Any other more elegant way to do this that perhaps doesn't require creating that fcNames
(form control names) variable, just for binding the correct names of the form controls in the template? It's duplicate code I'd love to eliminate if possible.