Security News
GitHub Removes Malicious Pull Requests Targeting Open Source Repositories
GitHub removed 27 malicious pull requests attempting to inject harmful code across multiple open source repositories, in another round of low-effort attacks.
@ngneat/reactive-forms
Advanced tools
(Angular Reactive) Forms with Benefits 😉
How many times have you told yourself "I wish Angular Reactive Forms would support types", or "I really want API to query the form reactively. It missed some methods."
Your wish is my command! This library extends every Angular AbstractControl
, 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! The only thing that you need to change is the import path. So don't worry, no form refactoring required - we've got you covered; One schematics command, and you're done!
Let's take a look at all the neat things we provide:
✅ Offers seamless FormControl
, FormGroup
, FormArray
Replacement
✅ Allows Typed Forms!
✅ Provides Reactive Queries
✅ Provides Helpful Methods
✅ Typed and DRY ControlValueAccessor
✅ Typed FormBuilder
👉 npm install @ngneat/reactive-forms
Each AbstractControl
takes a generic, which serves as the type
for any method exposed by Angular or this library:
Use it with a FormControl
:
import { FormControl } from '@ngneat/reactive-forms';
const control = new FormControl('');
control.valueChanges$.subscribe(value => {
// value is typed as string
});
Use it with a FormGroup
:
import { FormGroup } from '@ngneat/reactive-forms';
interface Profile {
firstName: string;
lastName: string;
address: {
street: string;
city: string;
};
}
const profileForm = new FormGroup<Profile>({
firstName: new FormControl(''),
lastName: new FormControl(''),
address: new FormGroup({
street: new FormControl(''),
city: new FormControl('')
})
});
// typed as Profile
profileForm.setValue(new Profile());
// typed as Partial<Profile>
profileForm.patchValue(new Profile());
Use it with a FormArray
:
import { FormArray, FormControl } from '@ngneat/reactive-forms';
const control = new FormArray<string>([new FormControl()]);
control.valueChanges$.subscribe(value => {
// value is typed as string[]
});
valueChanges$
Observes the control's value. Unlike the behavior of the built-in valueChanges
observable, it emits the current rawValue
immediately (which means you'll also get the values of disabled
controls).
import { FormControl } from '@ngneat/reactive-forms';
const control = new FormControl('');
control.valueChanges$.subscribe(value => ...);
disabledChanges$
Observes the control's disable
status.
import { FormControl } from '@ngneat/reactive-forms';
const control = new FormControl('');
control.disabledChanges$.subscribe(isDisabled => ...);
enabledChanges$
Observes the control's enable
status.
import { FormControl } from '@ngneat/reactive-forms';
const control = new FormControl('');
control.enabledChanges$.subscribe(isEnabled => ...);
statusChanges$
Observes the control's status
.
import { FormControl } from '@ngneat/reactive-forms';
const control = new FormControl('');
control.statusChanges$.subscribe(status => ...);
The status
is typed
as ControlState
(valid, invalid, pending or disabled).
touchChanges$
Observes the control's touched
status.
import { FormControl } from '@ngneat/reactive-forms';
const control = new FormControl('');
control.touchChanges$.subscribe(isTouched => ...);
This emits a value only when markAsTouched
, or markAsUnTouched
, has been called.
dirtyChanges$
Observes the control's dirty
status.
import { FormControl } from '@ngneat/reactive-forms';
const control = new FormControl('');
control.dirtyChanges$.subscribe(isDirty => ...);
This emits a value only when markAsDirty
, or markAsPristine
, has been called.
errorsChanges$
Observes the control's errors
.
import { FormControl } from '@ngneat/reactive-forms';
const control = new FormControl('');
control.errorsChanges$.subscribe(errors => ...);
select()
Selects a slice
of the form's state based on the given predicate.
import { FormGroup } from '@ngneat/reactive-forms';
const control = new FormGroup<Person>(...);
control.select(state => state.name).subscribe(name => ...)
setValue()
In addition to the built-in method functionality, it can also take an observable
.
import { FormGroup } from '@ngneat/reactive-forms';
const control = new FormGroup<Person>();
control.setValue(query.select('formValue'));
patchValue()
In addition to the built-in method functionality, it can take an observable
or a callback
function.
import { FormGroup } from '@ngneat/reactive-forms';
const control = new FormGroup<Person>();
control.patchValue(query.select('formValue'));
control.patchValue(state => ({
...state,
name: state.someProp ? 'someName' : 'anotherName'
}));
disabledWhile()
Takes an observable that emits a boolean indicating whether to disable
the control.
import { FormControl } from '@ngneat/reactive-forms';
const control = new FormControl('');
control.disabledWhile(query.select('isDisabled'));
enabledWhile()
Takes an observable that emits a boolean
indicating whether to enable
the control.
import { FormControl } from '@ngneat/reactive-forms';
const control = new FormControl('');
control.enabledWhile(query.select('isEnabled'));
mergeValidators()
Unlike the built-in setValidator()
method, it persists any existing validators.
import { FormControl } from '@ngneat/reactive-forms';
const control = new FormControl('', Validators.required);
control.mergeValidators(Validators.minLength(2));
control.mergeAsyncValidators(...);
markAllAsDirty()
Marks all the group's controls as dirty
.
import { FormGroup } from '@ngneat/reactive-forms';
const control = new FormGroup<Person>();
control.markAllAsDirty();
validateOn()
Takes an observable that emits a response, which is either null
or an error object (ValidationErrors
). The control's setErrors()
method is called whenever the source emits.
const passwordValidator = combineLatest([
this.signup.select(state => state.password),
this.signup.select(state => state.repeatPassword)
]).pipe(
map(([password, repeat]) => {
return password === repeat
? null
: {
isEqual: false
};
})
);
this.signup.validateOn(passwordValidator);
hasErrorAndTouched()
A syntactic sugar method to be used in the template:
import { FormControl } from '@ngneat/reactive-forms';
this.control = new FormControl('', Validators.required);
<span *ngIf="control.hasErrorAndTouched('required')"></span>
hasErrorAndDirty()
A syntactic sugar method to be used in the template:
import { FormControl } from '@ngneat/reactive-forms';
this.control = new FormControl('', Validators.required);
<span *ngIf="control.hasErrorAndDirty('required')"></span>
setEnable()
Sets whether the control is enabled
.
import { FormControl } from '@ngneat/reactive-forms';
const control = new FormControl('');
control.setEnable();
control.setEnable(false);
setDisable()
Sets whether the control is disabled
.
import { FormControl } from '@ngneat/reactive-forms';
const control = new FormControl('');
control.setDisable();
control.setDisable(false);
getControl()
A typed
method which obtains a reference to a specific control.
import { FormGroup } from '@ngneat/reactive-forms';
const group = new FormGroup<Person>(...);
const nameControl = group.getControl('name');
const nestedFieldControl = group.getControl('nested', 'field');
The array path variation of hasError()
, getError()
, and get()
is now typed
:
group.get(['phone', 'num']);
group.hasError('required', ['phone', 'num']);
group.getError('required', ['phone', 'num']);
Each AbstractControl
takes a second generic, which serves as the type of the errors:
type MyErrors = { isEqual: false };
const control = new FormControl<string, MyErrors>();
control.getError('isEqual'); // keyof MyErrors
control.hasError('isEqual'); // keyof MyErrors
// error type is MyErrors['isEqual']
const error = control.getError('isEqual'); // keyof MyErrors
The library provides a type for the built-in Angular validators types:
import { FormControl, NgValidatorsErrors } from '@ngneat/reactive-forms';
const control = new FormControl<string, NgValidatorsErrors>();
The library exposes a typed
version of ControlValueAccessor
, which already implements registerOnChange
and registerOnTouched
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
}
]
})
export class MyCheckboxComponent extends ControlValueAccessor<boolean> {
writeValue(value) {
// value is typed a boolean
}
// `this.onChange`, and `this.onTouched` are already here!
}
Note that you can also use it as interface
.
We also introduce a typed
version of FormBuilder
which returns a typed
FormGroup
, FormControl
and FormArray
with all our sweet additions:
import { FormBuilder } from '@ngneat/reactive-forms';
const fb = new FormBuilder();
// Returns a FormGroup<{name: string, id: number}>
const group = fb.group({ name: 'ngneat', id: 1 });
interface User {
userName: string;
email: string;
}
// We'll get an error because "id" does not exist in type `User`
const userGroup: FormGroup<User> = fb.group({ id: 1, userName: 'User', email: 'Email' });
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 the documentation.
The command will replace entities coming from @angular/reactive-forms
with @ngneat/reactive-forms
.
ng g @ngneat/reactive-forms:migrate
Further information about the script can be found here.
Thanks goes to these wonderful people (emoji key):
Netanel Basal 💻 📖 🤔 🚇 | Colum Ferry 💻 📖 | Dan Roujinsky 💻 📖 🤔 | Inbal Sinai 📖 | Itay Oded 💻 🤔 📖 ⚠️ 🔧 |
This project follows the all-contributors specification. Contributions of any kind welcome!
FAQs
(Angular Reactive) Forms with Benefits
The npm package @ngneat/reactive-forms receives a total of 3,852 weekly downloads. As such, @ngneat/reactive-forms popularity was classified as popular.
We found that @ngneat/reactive-forms demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 3 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
GitHub removed 27 malicious pull requests attempting to inject harmful code across multiple open source repositories, in another round of low-effort attacks.
Security News
RubyGems.org has added a new "maintainer" role that allows for publishing new versions of gems. This new permission type is aimed at improving security for gem owners and the service overall.
Security News
Node.js will be enforcing stricter semver-major PR policies a month before major releases to enhance stability and ensure reliable release candidates.