All Projects โ†’ ngneat โ†’ Reactive Forms

ngneat / Reactive Forms

Licence: mit
(Angular Reactive) Forms with Benefits ๐Ÿ˜‰

Programming Languages

typescript
32286 projects

Projects that are alternatives of or similar to Reactive Forms

Reactive forms
This is a model-driven approach to handling form inputs and validations, heavily inspired in Angular's Reactive Forms
Stars: โœญ 135 (-51.09%)
Mutual labels:  reactive, forms
Dirty Check Forms
๐ŸฌDetect Unsaved Changes in Angular Forms
Stars: โœญ 105 (-61.96%)
Mutual labels:  reactive, forms
Form
Form is an iOS Swift library for building and styling UIs
Stars: โœญ 99 (-64.13%)
Mutual labels:  reactive, forms
forms
A library to build declarative, composable, reactive user interfaces with WebSharper.
Stars: โœญ 12 (-95.65%)
Mutual labels:  reactive, forms
Survey Library
JavaScript Survey and Form Library
Stars: โœญ 3,060 (+1008.7%)
Mutual labels:  forms
js-form-validator
Javascript form validation. Pure JS. No jQuery
Stars: โœญ 38 (-86.23%)
Mutual labels:  forms
grav-plugin-form
Grav Form Plugin
Stars: โœญ 48 (-82.61%)
Mutual labels:  forms
callbag-rs
Rust implementation of the callbag spec for reactive/iterable programming
Stars: โœญ 25 (-90.94%)
Mutual labels:  reactive
Firefly
Firefly is an asynchronous web framework for rapid development of high-performance web application.
Stars: โœญ 277 (+0.36%)
Mutual labels:  reactive
Radioactive State
โ˜ข Make Your React App Truly Reactive!
Stars: โœญ 273 (-1.09%)
Mutual labels:  reactive
React Reactive Form
Angular like reactive forms in React.
Stars: โœญ 259 (-6.16%)
Mutual labels:  forms
Binder
An Annotation processor that allows binding two classes with each other, where the first class can listen to the updates of the second class ... ideal for MVVM and similar patterns
Stars: โœญ 21 (-92.39%)
Mutual labels:  reactive
Awesome Gravity Forms
A collection of third party add-ons for Gravity Forms plugin.
Stars: โœญ 267 (-3.26%)
Mutual labels:  forms
esm
Lightweight communicating state machine framework for embedded systems
Stars: โœญ 21 (-92.39%)
Mutual labels:  reactive
Purescript Flare
A special-purpose UI library for Purescript
Stars: โœญ 272 (-1.45%)
Mutual labels:  reactive
vertx-tracing
Vertx integration with tracing libraries
Stars: โœญ 21 (-92.39%)
Mutual labels:  reactive
Workflow Kotlin
A Swift and Kotlin library for making composable state machines, and UIs driven by those state machines.
Stars: โœญ 255 (-7.61%)
Mutual labels:  reactive
Formvuelate
Dynamic schema-based form rendering for VueJS
Stars: โœญ 262 (-5.07%)
Mutual labels:  forms
Card settings
A flutter package for building card based forms.
Stars: โœญ 256 (-7.25%)
Mutual labels:  forms
scrupulous
Simple inline form validation using HTML5 attributes that plays nicely with Bootstrap
Stars: โœญ 12 (-95.65%)
Mutual labels:  forms


Test MIT commitizen PRs styled with prettier All Contributors ngneat spectator

(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! In most cases, 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:

๐Ÿ”ฎ Features

โœ… Offers (almost) seamless FormControl, FormGroup, FormArray Replacement
โœ… Allows Typed Forms!
โœ… Provides Reactive Queries
โœ… Provides Helpful Methods
โœ… Typed and DRY ControlValueAccessor
โœ… Typed FormBuilder
โœ… Persist the form's state to local storage

๐Ÿ‘‰ npm install @ngneat/reactive-forms

Table of Contents

Control Type

Each AbstractControl takes a generic, which can be neither the value type (all AbstractControls) or the type of the controls (FormGroup/FormArray). This type is than used to enhance every method exposed by Angular or this library. Use it with a FormControl:

import { FormControl } from '@ngneat/reactive-forms';

const control = new FormControl<string>('');
control.valueChanges.subscribe(value => {
  // value is typed as string
});

Use it with a FormArray:

import { FormArray, FormControl } from '@ngneat/reactive-forms';

const control = new FormArray<string>([new FormControl()]);

control.value$.subscribe(value => {
  // value is typed as string[]
});

Use it with a FormGroup:

import { FormGroup, FormControl } 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({ firstName: 'Netanel' });

Or alternatively, with the controls as the generic type:

import { FormGroup, FormControl } from '@ngneat/reactive-forms';

interface ProfileControls {
  firstName: string; // Note that for primitive types the type "FormControl" is infered, so no need to write that.
  lastName: string;
  address: FormGroup<
    street: string,
    city: string,
  >;
}

const profileForm = new FormGroup<ProfileControls>({
  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({ firstName: 'Netanel' });

(Note supplying the controls type will enable you to access individual controls later with type inference, and avoid unneeded casting.)

Control Queries

value$

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.value$.subscribe(value => ...);

disabled$

Observes the control's disable status.

import { FormControl } from '@ngneat/reactive-forms';

const control = new FormControl('');
control.disabled$.subscribe(isDisabled => ...);

enabled$

Observes the control's enable status.

import { FormControl } from '@ngneat/reactive-forms';

const control = new FormControl('');
control.enabled$.subscribe(isEnabled => ...);

status$

Observes the control's status.

import { FormControl } from '@ngneat/reactive-forms';

const control = new FormControl('');
control.status$.subscribe(status => ...);

The status is typed as ControlState (valid, invalid, pending or disabled).

touch$

Observes the control's touched status.

import { FormControl } from '@ngneat/reactive-forms';

const control = new FormControl('');
control.touch$.subscribe(isTouched => ...);

This emits a value only when markAsTouched, or markAsUnTouched, has been called.

dirty$

Observes the control's dirty status.

import { FormControl } from '@ngneat/reactive-forms';

const control = new FormControl('');
control.dirty$.subscribe(isDirty => ...);

This emits a value only when markAsDirty, or markAsPristine, has been called.

errors$

Observes the control's errors.

import { FormControl } from '@ngneat/reactive-forms';

const control = new FormControl('');
control.errors$.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 => ...)

Control Methods

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 also take an observable.

import { FormGroup } from '@ngneat/reactive-forms';

const control = new FormGroup<Person>();
control.patchValue(query.select('formValue'));

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 method with typed parameters which obtains a reference to a specific control. When supplying the controls type, type inference for the returned control will be available for up to 2 keys in path. (Thus, for example getControl('a', 'b', 'c') will always return AbstractControl )

import { FormGroup } from '@ngneat/reactive-forms';

const group = new FormGroup<ProfileControls>(...);
const address: FormControl<string> = group.getControl('name');
const city: FormControl<string> = group.getControl('address', 'city');

Note that if you're only passing the "value" type (e.g. FormGroup<Profile>), this method will always return AbstractControl. In that case the return type might need to be inferred.

mergeErrors()

Merge validation errors. Unlike setErrors(), this will not overwrite errors already held by the control.

import { FormGroup } from '@ngneat/reactive-forms';

const group = new FormGroup<Profile>(...);
group.mergeErrors({ customError: true });

removeError()

Remove an error by key from the control.

import { FormGroup } from '@ngneat/reactive-forms';

const group = new FormGroup<Profile>(...);
group.removeError('customError');

FormArray methods

remove()

Remove a control from an array based on its value

import { FormArray } from '@ngneat/reactive-forms';

const array = new FormArray<string>(...);
// Remove empty strings
array.remove('')

removeIf()

Remove a control from an array based on a predicate

import { FormArray } from '@ngneat/reactive-forms';

const array = new FormArray<Profile>(...);
// Only keep addresses in NYC
array.removeIf((control) => control.get('address').get('city').value !== 'New York')

Control Path

The array path variation of hasError(), getError(), and get() is now typed:

const num = group.get(['phone', 'num']);
const hasError = group.hasError('required', ['phone', 'num']);
const getError = group.getError('required', ['phone', 'num']);

Control Errors

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>();

Control Operators

Each valueChanges or values$ takes an operator diff(), which emits only changed parts of form:

import { FormGroup, FormControl, diff } from '@ngneat/reactive-forms';

const control = new FormGroup<string>({
  name: new FormControl(''),
  phone: new FormGroup({
    num: new FormControl(),
    prefix: new FormControl()
  }),
  skills: new FormArray([])
});
control.value$
  .pipe(diff())
  .subscribe(value => {
    // value is emitted only if it has been changed, and only the changed parts.
  });

ControlValueAccessor

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: boolean) {

  }

  // `this.onChange`, and `this.onTouched` are already here!
}

Note that you can also use it as interface.

Form Builder

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' });

note: While the FormGroups/FormControls/etc created with our FormBuilder will have all additions, currently TS will not infer this, so one should still 'cast' them again on use:

const group = fb.group({
  userName: null,
  email: null
});

// will get TS error
group.controls.email.errors$.subscribe();

// will not get TS error
(group.controls.email as FormControl<string>).errors$.subscribe();

Persist Form

Automatically persist the FormGroup's value to the given storage:

const group = new FormGroup<Profile>();
const unsubscribe = group.persist('profile').subscribe();

The persist function will also set the FromGroup value to the latest state available in the storage before subscribing to value changes.

PersistOptions

Change the target storage or debounceTime value by providing options as a second argument in the persist function call.

Option Description Default
debounceTime Update delay in ms between value changes 250
manager A manager implementing the PersistManager interface LocalStorageManager
arrControlFactory Factory functions for FormArray
persistDisabledControls Defines whether values of disabled controls should be persisted false

By default the library provides LocalStorageManager and SessionStorageManager. It's possible to store the form value into a custom storage. Just implement the PersistManager interface, and use it when calling the persist function.

export class StateStoreManager<T> implements PersistManager<T> {
  setValue(key: string, data: T) {
     ...
  }

  getValue(key: string) {
    ...
  }
}

export class FormComponent implements OnInit {
  group = new FormGroup<Profile>();

  ngOnInit() {
    this.group.persist('profile', { manager: new StateStoreManager() }).subscribe();
  }
}
Using FormArray Controls.

When working with a FormArray, it's required to pass a factory function that defines how to create the controls inside the FormArray.

interface Profile {
  skills: string[];
}

const group = new FormGroup<Profile>({
  skills: new FormArray([])
});

group.persist('profile', {
  arrControlFactory: {
     skills: value => new FormControl(value)
  }
});

Because the form is strongly typed, you can only configure factories for properties that are of type Array. The library makes it also possible to correctly infer the type of value for the factory function.

ESLint Rule

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.

Migration

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.

Compatibility

Reactive-forms is compatible with Angular versions 8 and later.

Contributors โœจ

Thanks goes to these wonderful people (emoji key):

๐Ÿค” ๐Ÿš‡
Colum Ferry

๐Ÿ’ป ๐Ÿ“–
๐Ÿค”
Inbal Sinai

๐Ÿ“–
๐Ÿค” ๐Ÿ”ง
tehshin

๐Ÿ’ป ๐Ÿ“–

Mario Arnautou

๐Ÿ’ป

Har-Shuv

๐Ÿ’ป

This project follows the all-contributors specification. Contributions of any kind welcome!

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].