Angular TypeSafe Reactive Forms
Reactive FormGroups Wrapped in a Type Safe Class
- Improvements suggesions, bugs, errors, pull requests or become a contributor? Everything you need: https://github.com/pheetah/Angular-Type-Safe-Reactive-FormGroup
- Check this examples on stackblitz: https://stackblitz.com/edit/ng-typesafe-formgroup?file=src%2Fapp%2Fapp.component.html,src%2Fapp%2Fapp.component.ts
Installation and Basic Usage Guide
Implemented on Angular v13.2.x
Please click on dropdowns below for further information:
Installation and Basic Integration
-
To install the package just write the command:
npm i ng-typesafe-formgroup
-
Make sure that you import FormsModule and ReactiveFormsModule to the related module!
import { FormsModule, ReactiveFormsModule } from '@angular/forms'; @NgModule({ declarations: [ ... ], imports: [ ..., FormsModule, ReactiveFormsModule ], providers: [], bootstrap: [...] }) export class ...Module { }
Basic Usage
- Either use in strong-typed fashion
import { FormControlTypeSafe, FormGroupTypeSafe } from 'ng-typesafe-formgroup'; interface CustomInterface{ name: string, email: string, message: number, }; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.scss'] }) export class AppComponent { typeSafeFormGroup = new FormGroupTypeSafe<CustomInterface>({ name: new FormControlTypeSafe<CustomInterface["name"]>('', [Validators.required, Validators.minLength(5)]), email: new FormControlTypeSafe<CustomInterface["email"]>('', [Validators.required, Validators.maxLength(30)]), message: new FormControlTypeSafe<CustomInterface["message"]>('', [Validators.required, Validators.maxLength(100)]) }); \* you can now directly reach the controls! *\ ngOnInit(){ this.typeSafeFormGroup.controls.name this.typeSafeFormGroup.controls.message this.typeSafeFormGroup.controls.email this.typeSafeFormGroup.controls.email.value this.typeSafeFormGroup.controls.email.valueChanges.subscribe(a => console.log(a)); this.typeSafeFormGroup.valueChanges.subscribe(a => console.log(a)); } }
- Or use loose typing
import { FormControlTypeSafe, FormGroupTypeSafe } from 'ng-typesafe-formgroup'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.scss'] }) export class AppComponent { typeSafeFormGroupTypedWeak = new FormGroupTypeSafe({ x: new FormControlTypeSafe('', [Validators.required]) }); ngOnInit(){ this.typeSafeFormGroupTypedWeak.controls.x.value this.typeSafeFormGroupTypedWeak.controls.x.valueChanges.subscribe(a => console.log(a)); this.typeSafeFormGroupTypedWeak.valueChanges.subscribe(a => console.log(a)); } }
Discover the Package
Either use strong typing Flexibility
...
interface CustomInterface{
name: string,
email: string,
message: number,
};
...
typeSafeFormGroup = new FormGroupTypeSafe<CustomInterface>({
name: new FormControlTypeSafe<CustomInterface["name"]>('', [Validators.required, Validators.minLength(5)]),
email: new FormControlTypeSafe<CustomInterface["email"]>('', [Validators.required, Validators.maxLength(30)]),
message: new FormControlTypeSafe<CustomInterface["message"]>('', [Validators.required, Validators.maxLength(100)])
});
...
and this way forms will asset your values on typeSafeFormGroup.valueChanges and typeSafeFormGroup.value such as ;
typeSafeFormGroup.valueChanges.subscribe(val => val); \* val asserted as CustomInterface *\
typeSafeFormGroup.value \* value asserted as CustomInterface \*
so that value is asserted correctly, unlike normal FormGroup class asserts everything as any!
Or use loose typing. It still prevents you try to reach undefined control or mistype the control name in the group. But providing interface stricts you on form controls you add.
typeSafeFormGroupTypedWeak = new FormGroupTypeSafe({
x: new FormControl('', [Validators.required])
});
constructor(){}
ngOnInit(){
this.typeSafeFormGroupTypedWeak.controls.x /* OK!
this.typeSafeFormGroupTypedWeak.controls.a /* X->error! a is not a member of constructor object
}
Important note
Be careful on value assertions. If you declare and interface and provide it to form group, values asserted on property types of interface. For example:
interface CustomInterface{
name: string,
email: string,
message: number,
};
typeSafeFormGroup = new FormGroupTypeSafe<CustomInterface>({
name: new FormControlTypeSafe<CustomInterface["name"]>('', [Validators.required, Validators.minLength(5)]),
email: new FormControlTypeSafe<CustomInterface["email"]>('', [Validators.required, Validators.maxLength(30)]),
message: new FormControlTypeSafe<CustomInterface["message"]>('', [Validators.required, Validators.maxLength(100)])
});
this.typeSafeFormGroup.controls.email.value -> asserted as: string | number , because types on CustomInterfaces are string | number
this.typeSafeFormGroup.controls.email.valueChanges.subscribe(a => console.log(a)); -> 'a' asserted as string | number
this.typeSafeFormGroup.valueChanges.subscribe(a => console.log(a)); -> 'a' asserted as CustomInterface
this.typeSafeFormGroup.value -> asserted as CustomInterface
If you don't provide an interface values asserted as "unknown". If you want to assign the value for example to a string (you expect it coming from the form), just basically do:
typeSafeFormGroupTypedWeak = new FormGroupTypeSafe({
x: new FormControlTypeSafe('', [Validators.required])
});
let x:string = <unknown> this.typeSafeFormGroupTypedWeak.controls.x.value as string;
this.typeSafeFormGroupTypedWeak.controls.x.valueChanges.subscribe(a => a);
this.typeSafeFormGroupTypedWeak.controls.x.value -> asserted as unknown
Please note that you can still use FormControl inside FormGroupTypeSafe instead of FormControlTypeSafe, but it's recommended to use FormControlTypeSafe.
Type safe forms prevents you from making mistakes
- Transpiler warns you if you try to access an invalid control
typeSafeFormGroupTypedWeak = new FormGroupTypeSafe({
x: new FormGroupTypeSafe('', [Validators.required])
});
constructor(){}
ngOnInit(){
this.typeSafeFormGroupTypedWeak.controls.x /* OK!
this.typeSafeFormGroupTypedWeak.controls.a /* X->error! a is not a member of constructor object
}
- If you provide an interface, you can not define a control that is not defined in the interface you provide
interface FormControls{
name: string,
email: string,
message: number,
};
typeSafeFormGroup = new FormGroupTypeSafe<FormControls>({
name: new FormControlTypeSafe('', [Validators.required, Validators.minLength(5)]),
email: new FormControlTypeSafe('', [Validators.required, Validators.maxLength(30)]),
message: new FormControlTypeSafe('', [Validators.required, Validators.maxLength(100)]),
outlier: new FormControlTypeSafe('', [Validators.required, Validators.maxLength(100)]) // X-> error! 'outlier' is not FormControls property!
})