-
Notifications
You must be signed in to change notification settings - Fork 96
Migration Guide
- All validators have been ported over to instances of classes.
- A new validator
Validators.delegate()
has been introduced to bring the possibility of creating custom validators as separate functions or methods.
In versions previous to 15.0.0 validators were functions, but after 15.0.0 all validators (i.e. synchronous and asynchronous validators) have been ported over to classes that extend from the abstract class Validator
and AsyncValidator
.
This allows for a more consistent way of handling validators throughout the project. Individual functions|methods can still be used to create custom validators but now they must be used with the Validators.delegate()
or Validators.delegateAsync()
in order to provide them to the FormControls, FormGroups, and FormArrays.
Creating a class that extends from Validator
and overrides the validate
method:
/// Validator that validates the control's value must be `true`.
class RequiredTrueValidator extends Validator<dynamic> {
const RequiredTrueValidator() : super();
@override
Map<String, dynamic>? validate(AbstractControl<dynamic> control) {
return control.isNotNull &&
control.value is bool &&
control.value == true
? null
: {'requiredTrue': true};
}
}
Using the new validator:
final form = FormGroup({
'acceptLicense': FormControl<bool>(
value: false,
validators: [
RequiredTrueValidator(), // providing the new custom validator
],
),
});
Using Validators.delegate()
with a custom function to implement a custom validator
final form = FormGroup({
'acceptLicense': FormControl<bool>(
value: false,
validators: [
Validators.delegate(_requiredTrue) // delegates validation to a custom function
],
),
});
/// Custom function that validates that control's value must be `true`.
Map<String, dynamic>? _requiredTrue(AbstractControl<dynamic> control) {
return control.isNotNull &&
control.value is bool &&
control.value == true
? null
: {'requiredTrue': true};
}
- Change ReactiveFormField.validationMessages from a Map to a Function that receives the instance of the control and returns the Map with the customized validation messages.
This upgrade now brings the possibility to dynamically change the validation messages based on the current error data.
Example:
Given the form group definition:
final form = fb.group({
'amount': [5.0, Validators.required, Validators.min(10)],
});
ReactiveTextField(
formControlName: 'amount',
validatationMessages: (control) => {
ValidationMessages.required: 'The amount is required',
if (control.hasError(ValidationMessages.min))
ValidationMessages.min: 'The amount must have a minimum value of ${control.getError(ValidationMessages.min)['min']}'
}
),
Set a value directly to a FormControl from code do not marks the control as touched, you must explicitly call FormControl.markAsTouched() to show up validation messages in UI. Example:
set name(String newName) {
final formControl = this.form.control('name');
formControl.value = newName;
formControl.markAsTouched();// if newName is invalid then validation messages will show up in UI
}
Events renamed:
-
In AbstractControl
- onValueChanged to valueChanges
- onStatusChanged to statusChanges
- onTouched to touchChanges
-
In FormGroup and FormArray
- onCollectionChanged to collectionChanges
-
In FormControl
- onFocusChanged to focusChanges
All events are now Streams so previous codes like the following one:
final control = FormControl<String>();
// listen to control changes
control.onValueChanged.addListener((){
// must access the control to get the value
print(control.value);
});
control.value = 'Hello Reactive Forms!';
converts now to:
final control = FormControl<String>();
// listen to control changes
control.valueChanges.listen((String value){
// the value arrives from the arguments :)
print(value);
});
control.value = 'Hello Reactive Forms!';