Skip to content

Commit 3eea882

Browse files
committed
Merge branch 'release/18.2.0'
2 parents 3b53da4 + 88e1ef7 commit 3eea882

File tree

6 files changed

+203
-32
lines changed

6 files changed

+203
-32
lines changed

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,16 @@
1+
# 18.2.0
2+
3+
## Features
4+
5+
- Allowed overwriting default value on reset. Added an `overwriteDefaultValue` parameter to the
6+
`FormControl.reset()` method. When `overwriteDefaultValue` is `true`, the value passed to
7+
`reset()` becomes the new default value for the control.
8+
- Enhanced `FormControl` reset method. Introduced a `nonNullable` property to the `FormControl`
9+
constructor to control the behavior of the `reset()` method. When `nonNullable` is `true`
10+
(the default), calling `reset()` without a value will reset the control to its initial value. When
11+
`nonNullable` is `false`, calling `reset()` without a value will reset the control to `null`.
12+
- Exposed `defaultValue` of the `FormControl`.
13+
114
# 18.1.2
215

316
## Fixes

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ dependencies:
8888
flutter:
8989
sdk: flutter
9090

91-
reactive_forms: ^18.1.2
91+
reactive_forms: ^18.2.0
9292
```
9393
9494
Then, run the command `flutter packages get` in the console.

lib/src/models/models.dart

Lines changed: 97 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -839,7 +839,7 @@ abstract class AbstractControl<T> {
839839

840840
/// Tracks the value and validation status of an individual form control.
841841
class FormControl<T> extends AbstractControl<T> {
842-
final T? _initialValue;
842+
T? _defaultValue;
843843
final _focusChanges = StreamController<bool>.broadcast();
844844
FocusController? _focusController;
845845
bool _hasFocus = false;
@@ -848,6 +848,13 @@ class FormControl<T> extends AbstractControl<T> {
848848
///
849849
/// The control can optionally be initialized with a [value].
850850
///
851+
/// The [nonNullable] argument is used to determine the state of the control
852+
/// when the [reset] method is called without a value. If [nonNullable] is
853+
/// true (the default), the [reset] method will reset the control to the
854+
/// initial [value] provided in the constructor. If [nonNullable] is false,
855+
/// the [reset] method will reset the control to `null` unless a value is
856+
/// provided.
857+
///
851858
/// The control can optionally have [validators] that validates
852859
/// the control each time the value changes.
853860
///
@@ -859,10 +866,6 @@ class FormControl<T> extends AbstractControl<T> {
859866
/// (such as an HTTP request) if the more basic validation methods have
860867
/// already found invalid input.
861868
///
862-
/// You can set an [asyncValidatorsDebounceTime] in millisecond to set
863-
/// a delay time before trigger async validators. This is useful for
864-
/// minimizing request to a server. The default value is 250 milliseconds.
865-
///
866869
/// You can set [touched] as true to force the validation messages
867870
/// to show up at the very first time the widget that is bound to this
868871
/// control builds in the UI.
@@ -876,6 +879,7 @@ class FormControl<T> extends AbstractControl<T> {
876879
///
877880
FormControl({
878881
T? value,
882+
bool nonNullable = true,
879883
super.validators,
880884
super.asyncValidators,
881885
@Deprecated(
@@ -887,14 +891,24 @@ class FormControl<T> extends AbstractControl<T> {
887891
super.asyncValidatorsDebounceTime,
888892
super.touched,
889893
super.disabled,
890-
}) : _initialValue = value {
894+
}) : _defaultValue = nonNullable ? value : null {
891895
if (value != null) {
892896
this.value = value;
893897
} else {
894898
updateValueAndValidity();
895899
}
896900
}
897901

902+
/// Gets the default value of the control.
903+
///
904+
/// This value is determined by the [value] and [nonNullable] arguments
905+
/// passed to the constructor:
906+
/// - If [nonNullable] is `true` (the default), this holds the initial [value].
907+
/// - If [nonNullable] is `false`, this is `null`.
908+
///
909+
/// When [reset] is called without a value, the control resets to this value.
910+
T? get defaultValue => _defaultValue;
911+
898912
/// True if the control is marked as focused.
899913
bool get hasFocus => _hasFocus;
900914

@@ -1016,21 +1030,92 @@ class FormControl<T> extends AbstractControl<T> {
10161030
}
10171031
}
10181032

1033+
/// Resets the form control, marking it as untouched and pristine.
1034+
///
1035+
/// If [value] is provided, the control is reset to that value.
1036+
///
1037+
/// If [value] is not provided (null), the behavior depends on the [nonNullable]
1038+
/// argument passed to the constructor:
1039+
/// - If [nonNullable] is `true` (the default), the control resets to the
1040+
/// initial value provided in the constructor.
1041+
/// - If [nonNullable] is `false`, the control resets to `null`.
1042+
///
1043+
/// If [overwriteDefaultValue] is true, then the value used to reset the
1044+
/// control becomes the new default value of the control.
1045+
///
1046+
/// The argument [disabled] is optional and resets the disabled status of the
1047+
/// control. If value is `true` then it will disable the control, if value is
1048+
/// `false` then it will enable the control, and if the value is `null` or
1049+
/// not set (the default) then the control will state in the same state that
1050+
/// it previously was.
1051+
///
1052+
/// The argument [removeFocus] is optional and remove the UI focus from the
1053+
/// control.
1054+
///
1055+
/// When [updateParent] is true or not supplied (the default) each change
1056+
/// affects this control and its parent, otherwise only affects to this
1057+
/// control.
1058+
///
1059+
/// When [emitEvent] is true or not supplied (the default), both the
1060+
/// *statusChanges* and *valueChanges* events notify listeners with the
1061+
/// latest status and value when the control is reset. When false, no events
1062+
/// are emitted.
1063+
///
1064+
/// ### Examples
1065+
///
1066+
/// **Reset to a specific value**
1067+
/// ```dart
1068+
/// final control = FormControl<String>();
1069+
///
1070+
/// control.reset(value: 'John Doe');
1071+
///
1072+
/// print(control.value); // output: 'John Doe'
1073+
/// ```
1074+
///
1075+
/// **Reset to initial value (nonNullable: true)**
1076+
/// ```dart
1077+
/// // nonNullable is true by default
1078+
/// final control = FormControl<String>(value: 'Initial Value');
1079+
///
1080+
/// control.value = 'New Value';
1081+
///
1082+
/// // Resets to 'Initial Value' because no value was provided
1083+
/// // and nonNullable is true.
1084+
/// control.reset();
1085+
///
1086+
/// print(control.value); // output: 'Initial Value'
1087+
/// ```
1088+
///
1089+
/// **Reset to null (nonNullable: false)**
1090+
/// ```dart
1091+
/// final control = FormControl<String>(
1092+
/// value: 'Initial Value',
1093+
/// nonNullable: false,
1094+
/// );
1095+
///
1096+
/// control.value = 'New Value';
1097+
///
1098+
/// // Resets to null because no value was provided
1099+
/// // and nonNullable is false.
1100+
/// control.reset();
1101+
///
1102+
/// print(control.value); // output: null
1103+
///
10191104
@override
10201105
void reset({
10211106
T? value,
1107+
bool overwriteDefaultValue = false,
10221108
bool updateParent = true,
10231109
bool emitEvent = true,
10241110
bool removeFocus = false,
10251111
bool? disabled,
10261112
}) {
1027-
// If `value` is null, it implies either `reset()` was called (no explicit value)
1028-
// or `reset(value: null)` was called.
1029-
// In line with "If no value is provided it should assign the initial value",
1030-
// we use `_initialValue` when `value` is `null`.
1031-
// If `value` is explicitly provided and not null, we use that.
1113+
if (overwriteDefaultValue) {
1114+
_defaultValue = value;
1115+
}
1116+
10321117
super.reset(
1033-
value: value ?? _initialValue,
1118+
value: value ?? _defaultValue,
10341119
updateParent: updateParent,
10351120
emitEvent: emitEvent,
10361121
removeFocus: removeFocus,

pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: reactive_forms
22
description: This is a model-driven approach to handling form inputs and validations, heavily inspired in Angular Reactive Forms.
3-
version: 18.1.2
3+
version: 18.2.0
44
homepage: "https://github.com/joanpablo/reactive_forms"
55

66
environment:

test/src/models/form_control_test.dart

Lines changed: 91 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -415,7 +415,7 @@ void main() {
415415
'resets to initial value (null) if no argument is provided and initial value was null',
416416
() {
417417
// Arrange
418-
final control = FormControl<String?>(value: null);
418+
final control = FormControl<String>(value: null);
419419
control.updateValue('changed');
420420

421421
// Act
@@ -430,7 +430,7 @@ void main() {
430430
'resets to initial value (null) if no argument is provided and no initial value was specified (implicitly null)',
431431
() {
432432
// Arrange
433-
final control = FormControl<String?>();
433+
final control = FormControl<String>();
434434
control.updateValue('changed');
435435

436436
// Act
@@ -460,7 +460,7 @@ void main() {
460460
'resets to initial value when value argument is null and initial value was not null',
461461
() {
462462
// Arrange
463-
final control = FormControl<String?>(value: 'initial');
463+
final control = FormControl<String>(value: 'initial');
464464
control.updateValue('changed');
465465

466466
// Act
@@ -470,25 +470,104 @@ void main() {
470470
expect(
471471
control.value,
472472
'initial',
473-
); // Because (value ?? _initialValue) => (null ?? "initial") => "initial"
473+
); // Because nonNullable is true by default
474474
},
475475
);
476476

477+
test('resets to null if nonNullable is false', () {
478+
// Given: a control with an initial value and nonNullable as false
479+
final control = FormControl<String>(
480+
value: 'initialValue',
481+
nonNullable: false,
482+
);
483+
484+
// When: set a new value
485+
control.value = 'changed value';
486+
487+
// And: reset the control
488+
control.reset();
489+
490+
// Then: control value is null
491+
expect(control.value, null);
492+
});
493+
477494
test(
478-
'resets to null when value argument is null and initial value was also null',
495+
'resets control to null if no value provided and nonNullable is false',
479496
() {
480497
// Arrange
481-
final control = FormControl<String?>(value: null);
482-
control.updateValue('changed');
498+
final control = FormControl<String>(nonNullable: false);
499+
control.value = 'changed value';
483500

484501
// Act
485-
control.reset(value: null);
502+
control.reset();
486503

487504
// Assert
488-
expect(
489-
control.value,
490-
null,
491-
); // Because (value ?? _initialValue) => (null ?? null) => null
505+
expect(control.value, null); // Because no value provided
506+
},
507+
);
508+
});
509+
510+
group('FormControl reset with overwriteDefaultValue', () {
511+
test('updates default value when overwriteDefaultValue is true', () {
512+
// Given: a control with an initial value
513+
final control = FormControl<String>(value: 'initial');
514+
515+
// When: reset to a new value and ask to overwrite the default
516+
control.reset(value: 'newDefault', overwriteDefaultValue: true);
517+
518+
// Then: current value is the new value
519+
expect(control.value, 'newDefault');
520+
// And: the default value property is updated
521+
expect(control.defaultValue, 'newDefault');
522+
523+
// When: we make the control dirty again
524+
control.value = 'dirty value';
525+
526+
// And: we reset without arguments
527+
control.reset();
528+
529+
// Then: it resets to the NEW default value
530+
expect(control.value, 'newDefault');
531+
});
532+
533+
test(
534+
'does not update default value when overwriteDefaultValue is false',
535+
() {
536+
// Given: a control with an initial value
537+
final control = FormControl<String>(value: 'initial');
538+
539+
// When: reset with a specific value but do NOT overwrite default
540+
control.reset(value: 'tempValue', overwriteDefaultValue: false);
541+
542+
// Then: value is the temporary value
543+
expect(control.value, 'tempValue');
544+
// And: default value remains the original one
545+
expect(control.defaultValue, 'initial');
546+
547+
// When: we make the control dirty again
548+
control.value = 'dirty value';
549+
550+
// And: we reset without arguments
551+
control.reset();
552+
553+
// Then: it resets to the ORIGINAL default value
554+
expect(control.value, 'initial');
555+
},
556+
);
557+
558+
test(
559+
'updates default value to null when value is null and overwriteDefaultValue is true',
560+
() {
561+
// Given: a control with an initial value
562+
final control = FormControl<String>(value: 'initial');
563+
564+
// When: reset to null and overwrite default
565+
control.reset(value: null, overwriteDefaultValue: true);
566+
567+
// Then: value is null
568+
expect(control.value, null);
569+
// And: default value is null
570+
expect(control.defaultValue, null);
492571
},
493572
);
494573
});

test/src/models/form_group_test.dart

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -288,14 +288,8 @@ void main() {
288288
});
289289

290290
// When: enable form
291-
print("form.enabled: ${form.enabled}");
292-
293291
form.markAsEnabled();
294292

295-
form.controls.forEach((name, control) {
296-
print("control[$name].enabled: ${control.enabled}");
297-
});
298-
299293
// Then: all controls are enabled
300294
expect(
301295
form.controls.values.every((control) => control.enabled),

0 commit comments

Comments
 (0)