Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork56
(Angular Reactive) Forms with Benefits 😉
ngneat/reactive-forms
Folders and files
| Name | Name | Last commit message | Last commit date | |
|---|---|---|---|---|
Repository files navigation
(Angular Reactive) Forms with Benefits 😉
How many times have you told yourself "I wish Angular Reactive Forms would support types", or "I really want an API to query the form reactively. It missed some methods."
Your wish is my command!
This library extends every AngularAbstractControl, and provides features that don't exist in the original one. It adds types, reactive queries, and helper methods. The most important thing is that you can start using it today! In most cases, the only thing that you need to change is theimport path. So don't worry, no form refactoring required - we've got you covered;
Let's take a look at all the neat things we provide:
✅ Offers (almost) seamlessFormControl,FormGroup,FormArray Replacement
✅ Allows Typed Forms!
✅ Provides Reactive Queries
✅ Provides Helpful Methods
✅ Typed and DRYControlValueAccessor
✅ TypedFormBuilder
✅ Persist the form's state to local storage
👉 npm install @ngneat/reactive-forms- Control Type
- Control Queries
- Control Methods
- Control Operators
- ControlValueAccessor
- Form Builder
- Persist Form
- ESLint Rule
FormControl/FormArray takes a generic that defines thetype of the control. Thistype is then used to enhance every method exposed by Angular or this library.
Use it with aFormControl:
import{FormControl}from'@ngneat/reactive-forms';constcontrol=newFormControl<string>();// Or auto infer it based on the initial valueconstcontrol=newFormControl('');control.valueChanges.subscribe(value=>{// value is typed as string});
Use it with aFormArray:
import{FormArray,FormControl}from'@ngneat/reactive-forms';constcontrol=newFormArray<string>([newFormControl('')]);control.value$.subscribe(value=>{// value is typed as string[]});
If you use aFormGroup, it'll automatically infer thetype based on thecontrols you supply:
import{FormGroup,FormControl}from'@ngneat/reactive-forms';constprofileForm=newFormGroup({firstName:newFormControl(''),lastName:newFormControl(''),address:newFormGroup({street:newFormControl(''),city:newFormControl('')})});profileForm.setValue(newProfile());profileForm.patchValue({firstName:'Netanel'});
You can use theexperimentalControlsOf feature if you want to force aFormGroup to implement an externaltype:
import{FormGroup,FormControl,ControlsOf}from'@ngneat/reactive-forms';interfaceProfile{firstName:string;lastName:string;address:{street:string;city:string;};}constprofileForm=newFormGroup<ControlsOf<Profile>>({firstName:newFormControl(''),lastName:newFormControl(''),address:newFormGroup({street:newFormControl(''),city:newFormControl('')})});
- When using
arraytypes, it'll automatically infer it asFormArray. If you need aFormControl, you must set it within your interface explicitly:
interfaceUser{name:string;// 👇🏻skills:FormControl<string[]>;}
- Optional fields will only work with top-level values, and will not work with
FormGroup:
interfaceUser{name?:string;foo?:string[];// 👇🏻 will not worknested?:{id:string;};}
Observes the control's value. Unlike the behavior of the built-invalueChanges observable, it emits the currentrawValueimmediately (which means you'll also get the values ofdisabled controls).
import{FormControl}from'@ngneat/reactive-forms';constcontrol=newFormControl('');control.value$.subscribe(value=> ...);
Observes the control'sdisable status.
import{FormControl}from'@ngneat/reactive-forms';constcontrol=newFormControl('');control.disabled$.subscribe(isDisabled=> ...);
Observes the control'senable status.
import{FormControl}from'@ngneat/reactive-forms';constcontrol=newFormControl('');control.enabled$.subscribe(isEnabled=> ...);
Observes the control'sinvalid status.
import{FormControl}from'@ngneat/reactive-forms';constcontrol=newFormControl('');control.invalid$.subscribe(isInvalid=> ...);
Observes the control'svalid status.
import{FormControl}from'@ngneat/reactive-forms';constcontrol=newFormControl('');control.valid$.subscribe(isValid=> ...);
Observes the control'sstatus.
import{FormControl}from'@ngneat/reactive-forms';constcontrol=newFormControl('');control.status$.subscribe(status=> ...);
Thestatus istyped asControlState (valid, invalid, pending or disabled).
Observes the control'stouched status.
import{FormControl}from'@ngneat/reactive-forms';constcontrol=newFormControl('');control.touch$.subscribe(isTouched=> ...);
This emits a valueonly whenmarkAsTouched, ormarkAsUnTouched, has been called.
Observes the control'sdirty status.
import{FormControl}from'@ngneat/reactive-forms';constcontrol=newFormControl('');control.dirty$.subscribe(isDirty=> ...);
This emits a valueonly whenmarkAsDirty, ormarkAsPristine, has been called.
Observes the control'serrors.
import{FormControl}from'@ngneat/reactive-forms';constcontrol=newFormControl('');control.errors$.subscribe(errors=> ...);
Selects aslice of the form's state based on the given predicate.
import{FormGroup}from'@ngneat/reactive-forms';constgroup=newFormGroup({name:newFormControl('')});group.select(state=>state.name).subscribe(name=> ...)
In addition to the built-in method functionality, it can also take anobservable.
import{FormGroup}from'@ngneat/reactive-forms';constgroup=newFormGroup({name:newFormControl('')});group.setValue(store.select('formValue'));
In addition to the built-in method functionality, it can also take anobservable.
import{FormGroup}from'@ngneat/reactive-forms';constgroup=newFormGroup({name:newFormControl('')});group.patchValue(store.select('formValue'));
Takes an observable that emits a boolean indicating whether todisable the control.
import{FormControl}from'@ngneat/reactive-forms';constcontrol=newFormControl('');control.disabledWhile(store.select('isDisabled'));
Takes an observable that emits aboolean indicating whether toenable the control.
import{FormControl}from'@ngneat/reactive-forms';constcontrol=newFormControl('');control.enabledWhile(store.select('isEnabled'));
Marks all the group's controls asdirty.
import{FormGroup}from'@ngneat/reactive-forms';constcontrol=newFormGroup();control.markAllAsDirty();
A syntactic sugar method to be used in the template:
import{FormControl}from'@ngneat/reactive-forms';constcontrol=newFormControl('',Validators.required);
<span*ngIf="control.hasErrorAndTouched('required')"></span>
A syntactic sugar method to be used in the template:
import{FormControl}from'@ngneat/reactive-forms';constcontrol=newFormControl('',Validators.required);
<span*ngIf="control.hasErrorAndDirty('required')"></span>
Sets whether the control isenabled.
import{FormControl}from'@ngneat/reactive-forms';constcontrol=newFormControl('');control.setEnable();control.setEnable(false);
Sets whether the control isdisabled.
import{FormControl}from'@ngneat/reactive-forms';constcontrol=newFormControl('');control.setDisable();control.setDisable(false);
A method withtyped parameters which obtains a reference to a specific control.
import{FormGroup}from'@ngneat/reactive-forms';constgroup=newFormGroup({name:newFormControl(''),address:newFormGroup({street:newFormControl(''),city:newFormControl('')})});constname=group.get('name');// FormControl<string>constcity=group.get(['address','city']);// FormControl<string>// Don't use it like thisgroup.get('address.city')// AbstractControl
Merge validation errors. UnlikesetErrors(), this will not overwrite errors already held by the control.
import{FormGroup}from'@ngneat/reactive-forms';constgroup=newFormGroup(...);group.mergeErrors({customError:true});
Remove an error by key from the control.
import{FormGroup}from'@ngneat/reactive-forms';constgroup=newFormGroup(...);group.removeError('customError');
Remove a control from an array based on its value
import{FormArray}from'@ngneat/reactive-forms';constarray=newFormArray<string>(...);// Remove empty stringsarray.remove('')
Remove a control from an array based on a predicate
import{FormArray}from'@ngneat/reactive-forms';constarray=newFormArray(...);// Only keep addresses in NYCarray.removeIf((control)=>control.get('address').get('city').value!=='New York')
EachvalueChanges orvalues$ takes an operatordiff(), which emits only changed parts of form:
import{FormGroup,FormControl,diff}from'@ngneat/reactive-forms';constcontrol=newFormGroup({name:newFormControl(''),phone:newFormGroup({num:newFormControl(''),prefix:newFormControl('')}),skills:newFormArray<string>([])});control.value$.pipe(diff()).subscribe(value=>{// value is emitted only if it has been changed, and only the changed parts.});
The library exposes atyped version ofControlValueAccessor, which already implementsregisterOnChange andregisterOnTouched under the hood:
import{ControlValueAccessor}from'@ngneat/reactive-forms';@Component({selector:'my-checkbox',host:{'(change)':'onChange($event.target.checked)','(blur)':'onTouched()'},providers:[{provide:NG_VALUE_ACCESSOR,useExisting:MyCheckboxComponent,multi:true}]})exportclassMyCheckboxComponentextendsControlValueAccessor<boolean>{writeValue(value:boolean){}// `this.onChange`, and `this.onTouched` are already here!}
Note that you can also use it asinterface.
We also introduce atyped version ofFormBuilder which returns atypedFormGroup,FormControl andFormArray with all our sweet additions:
import{FormBuilder}from'@ngneat/reactive-forms';constructor(privatefb:FormBuilder){}constgroup=this.fb.group({name:'ngneat',id:1});group.get('name')// FormControl<string>
Due to the complexity of the builder API, we are currently couldn't create a "good" implementation ofControlsOf for the builder.
Automatically persist theAbstractControl's value to the given storage:
import{persistControl}from'@ngneat/reactive-forms';constgroup=newFormGroup(...);constunsubscribe=persistControl(group,'profile').subscribe();
ThepersistControl function will also set theFromGroup value to the latest state available in the storage before subscribing to value changes.
Change the target storage ordebounceTime value by providing options as a second argument in thepersist function call.
| Option | Description | Default |
|---|---|---|
debounceTime | Update delay in ms between value changes | 250 |
manager | A manager implementing thePersistManager interface | LocalStorageManager |
arrControlFactory | Factory functions forFormArray | |
persistDisabledControls | Defines whether values of disabled controls should be persisted | false |
By default the library providesLocalStorageManager andSessionStorageManager. It's possible to store the form value into a custom storage. Just implement thePersistManager interface, and use it when calling thepersistControl function.
exportclassStateStoreManager<T>implementsPersistManager<T>{setValue(key:string,data:T){ ...}getValue(key:string){ ...}}exportclassFormComponentimplementsOnInit{group=newFormGroup();ngOnInit(){persist(this.group,'profile',{manager:newStateStoreManager()}).subscribe();}}
When working with aFormArray, it's required to pass afactory function that defines how to create thecontrols inside theFormArray.
constgroup=newFormGroup({skills:newFormArray<string>([])});persist(group,'profile',{arrControlFactory:{skills:value=>newFormControl(value)}});
Because the form is strongly typed, you can only configure factories for properties that are of typeArray. The library makes it also possible to correctly infer the type ofvalue for the factory function.
We provide a special lint rule that forbids the imports of any token we expose, such as the following:AbstractControl,AsyncValidatorFn,ControlValueAccessor,FormArray,FormBuilder,FormControl,FormGroup,ValidatorFn,from@angular/forms.
Check out thedocumentation.
About
(Angular Reactive) Forms with Benefits 😉
Topics
Resources
Code of conduct
Contributing
Uh oh!
There was an error while loading.Please reload this page.
Stars
Watchers
Forks
Sponsor this project
Uh oh!
There was an error while loading.Please reload this page.
Packages0
Uh oh!
There was an error while loading.Please reload this page.