Last active
March 20, 2022 13:17
-
-
Save abbazabacto/0f290e1ef72e682a9a1309c0cfa81bf6 to your computer and use it in GitHub Desktop.
Typed Form Builder
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// extended upon: https://ruanbeukes.net/Angular-Typesafe-Reactive-Forms/ | |
import { Injectable } from '@angular/core'; | |
import { AbstractControl, FormArray, FormControl, FormGroup, ValidatorFn, AsyncValidatorFn, FormBuilder } from '@angular/forms'; | |
import { Observable } from 'rxjs/Observable'; | |
type PropertyFn<T, R> = (val: T) => R; | |
interface AbstractControlTyped<T> extends AbstractControl { | |
readonly value: T; | |
readonly valueChanges: Observable<T>; | |
setValue(value: T, options?: Object): T; | |
patchValue(value: T, options?: Object): T; | |
reset(value?: T, options?: Object): void; | |
get(path: Array<string | number> | string): AbstractControl | null; | |
get<R>(path: Array<string | number> | string): AbstractControlTyped<R> | null; | |
getSafe<R>(propertyFn: PropertyFn<T, R>): AbstractControlTyped<R> | null; | |
} | |
interface FormGroupTyped<T> extends FormGroup { | |
readonly value: T; | |
readonly valueChanges: Observable<T>; | |
setValue(value: T, options?: Object): T; | |
patchValue(value: T, options?: Object): T; | |
reset(value?: T, options?: Object): void; | |
get(path: Array<string | number> | string): AbstractControl | null; | |
get<T>(path: Array<string | number> | string): AbstractControlTyped<T> | null; | |
getSafe<R>(propertyFn: PropertyFn<T, R>): AbstractControlTyped<R> | null; | |
registerControl(name: string, control: AbstractControl): AbstractControl; | |
registerControl<T>(name: string, control: AbstractControl): AbstractControlTyped<T>; | |
registerControlSafe<R>(propertyFn: PropertyFn<T, R>, control: AbstractControl): AbstractControlTyped<R>; | |
addControlSafe<R>(propertyFn: PropertyFn<T, R>, control: AbstractControl): void; | |
removeControlSafe<R>(propertyFn: PropertyFn<T, R>): void; | |
setControlSafe<R>(propertyFn: PropertyFn<T, R>, control: AbstractControl): void; | |
} | |
export type ControlsConfigTyped<T> = { | |
[P in keyof T]: FormControl | FormGroup | FormArray | undefined[] | { | |
0?: T[P]; | |
1?: ValidatorFn | ValidatorFn[]; | |
2?: AsyncValidatorFn | AsyncValidatorFn[]; | |
}; | |
}; | |
@Injectable() | |
export class FormBuilderTyped extends FormBuilder { | |
private static getPropertyName(propertyFunction: Function): string { | |
const properties = propertyFunction.toString() | |
.match(/(?![. ])([a-z0-9_]+)(?=[};.])/gi) | |
.splice(1); | |
return properties.join('.'); | |
} | |
private static getSafe<T, R>(group: AbstractControl, propertyFn: PropertyFn<T, R>): AbstractControlTyped<R> { | |
const getStr = FormBuilderTyped.getPropertyName(propertyFn); | |
return this.mapControl(group.get(getStr) as AbstractControlTyped<R>); | |
} | |
private static registerControlSafe<T, R>(group: FormGroup, propertyFn: PropertyFn<T, R>, control: AbstractControl): AbstractControlTyped<R> { | |
const getStr = FormBuilderTyped.getPropertyName(propertyFn); | |
return this.mapControl(group.registerControl(getStr, control) as AbstractControlTyped<R>); | |
} | |
private static addControlSafe<T, R>(group: FormGroup, propertyFn: PropertyFn<T, R>, control: AbstractControl) { | |
const getStr = FormBuilderTyped.getPropertyName(propertyFn); | |
group.addControl(getStr, control); | |
} | |
private static removeControlSafe<T, R>(group: FormGroup, propertyFn: PropertyFn<T, R>) { | |
const getStr = FormBuilderTyped.getPropertyName(propertyFn); | |
group.removeControl(getStr); | |
} | |
private static setControlSafe<T, R>(group: FormGroup, propertyFn: PropertyFn<T, R>, control: AbstractControl) { | |
const getStr = FormBuilderTyped.getPropertyName(propertyFn); | |
group.setControl(getStr, control); | |
} | |
private static mapFormGroup<T>(group: FormGroupTyped<T>): FormGroupTyped<T> { | |
group.registerControlSafe = function<T, R>(propertyFn: PropertyFn<T, R>, control: AbstractControl) { | |
return FormBuilderTyped.registerControlSafe(group, propertyFn, control); | |
}; | |
group.addControlSafe = function<T, R>(propertyFn: PropertyFn<T, R>, control: AbstractControl) { | |
FormBuilderTyped.addControlSafe(group, propertyFn, control); | |
}; | |
group.removeControlSafe = function<T, R>(propertyFn: PropertyFn<T, R>) { | |
FormBuilderTyped.removeControlSafe(group, propertyFn); | |
}; | |
group.setControlSafe = function<T, R>(propertyFn: PropertyFn<T, R>, control: AbstractControl) { | |
return FormBuilderTyped.setControlSafe(group, propertyFn, control); | |
}; | |
return group; | |
} | |
private static mapControl<T>(control: FormGroupTyped<T>): FormGroupTyped<T>; | |
private static mapControl<T>(control: AbstractControlTyped<T>): AbstractControlTyped<T>; | |
private static mapControl<T>(control: AbstractControlTyped<T> | FormGroupTyped<T>): AbstractControlTyped<T> { | |
control.getSafe = function<T, R>(propertyFn: PropertyFn<T, R>) { | |
return FormBuilderTyped.getSafe(control, propertyFn); | |
}; | |
if (control instanceof FormGroup) { | |
this.mapFormGroup(control); | |
} | |
return control; | |
} | |
group(controlsConfig: ControlsConfigTyped<any>, extra?: { [key: string]: any; } | null): FormGroup; | |
group<T>(controlsConfig: ControlsConfigTyped<T>, extra?: { [key: string]: any; } | null): FormGroupTyped<T>; | |
group<T>(controlsConfig: ControlsConfigTyped<T>, extra?: { [key: string]: any; } | null): FormGroupTyped<T> { | |
let group = super.group(controlsConfig, extra) as FormGroupTyped<T>; | |
if (group) { | |
return FormBuilderTyped.mapControl(group); | |
} | |
} | |
} |
@florestankorp my recommendation would be to wait for official support for strongly typed forms, it's finally coming: angular/angular#44513
If you do feel like trying it out, it's a strongly typed version of Formbuilder.group
which creates a FormGroup
with strongly typed variants of FormControl
and AbstractControl
. You have to add add the types yourself, they aren't inferred.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Could you add an example of how to use this?