From 77b06053be63ddc554a0abf9b3050387ea6cd0ba Mon Sep 17 00:00:00 2001 From: ahmednfwela Date: Mon, 3 Mar 2025 10:33:01 +0200 Subject: [PATCH 1/3] feat: support controlFactory in both FormGroup and FormArray --- lib/src/models/form_builder.dart | 4 +++ lib/src/models/models.dart | 50 +++++++++++++++++++++++++++- test/src/models/form_array_test.dart | 45 +++++++++++++++++++++++++ 3 files changed, 98 insertions(+), 1 deletion(-) diff --git a/lib/src/models/form_builder.dart b/lib/src/models/form_builder.dart index f3f6352e..af64954b 100644 --- a/lib/src/models/form_builder.dart +++ b/lib/src/models/form_builder.dart @@ -63,6 +63,7 @@ class FormBuilder { Map controls, [ List> validators = const [], List> asyncValidators = const [], + FormGroupControlFactory? controlFactory, ]) { final map = controls .map>((String key, Object value) { @@ -117,6 +118,7 @@ class FormBuilder { map, validators: validators, asyncValidators: asyncValidators, + controlFactory: controlFactory, ); } @@ -203,6 +205,7 @@ class FormBuilder { List value, [ List> validators = const [], List> asyncValidators = const [], + FormArrayControlFactory? controlFactory, ]) { return FormArray( value.map>((v) { @@ -217,6 +220,7 @@ class FormBuilder { }).toList(), validators: validators, asyncValidators: asyncValidators, + controlFactory: controlFactory, ); } diff --git a/lib/src/models/models.dart b/lib/src/models/models.dart index 9d809f32..e3e6d201 100644 --- a/lib/src/models/models.dart +++ b/lib/src/models/models.dart @@ -1098,6 +1098,9 @@ abstract class FormControlCollection extends AbstractControl { } } +typedef FormGroupControlFactory = AbstractControl? Function( + String key, Object? value); + /// Tracks the value and validity state of a group of FormControl instances. /// /// A FormGroup aggregates the values of each child FormControl into one object, @@ -1109,6 +1112,20 @@ abstract class FormControlCollection extends AbstractControl { class FormGroup extends FormControlCollection> { final Map> _controls = {}; + /// A function that maps a value to a control. + /// Used in [updateValue] to create a new control for new values. + final FormGroupControlFactory? controlFactory; + + static AbstractControl? defaultControlFactory( + String key, + Object? value, + ) { + return null; + } + + FormGroupControlFactory get controlFactoryOrDefault => + controlFactory ?? defaultControlFactory; + /// Creates a new FormGroup instance. /// /// When instantiating a [FormGroup], pass in a [Map] of child controls @@ -1149,6 +1166,7 @@ class FormGroup extends FormControlCollection> { super.asyncValidators, super.asyncValidatorsDebounceTime, bool disabled = false, + this.controlFactory, }) : assert( !controls.keys.any((name) => name.contains(_controlNameDelimiter)), 'Control name should not contain dot($_controlNameDelimiter)'), @@ -1431,6 +1449,20 @@ class FormGroup extends FormControlCollection> { ); } + // Add missing controls via controlFactory, similar to FormArray + final controlFactory = controlFactoryOrDefault; + final newControls = Map.fromEntries( + value.entries + .where((e) => !_controls.containsKey(e.key)) + .map((e) => MapEntry(e.key, controlFactory(e.key, e.value))) + .where((e) => e.value != null), + ).cast>(); + if (newControls.isNotEmpty) { + _controls.addAll(newControls); + newControls.forEach((name, control) { + control.parent = this; + }); + } updateValueAndValidity( updateParent: updateParent, emitEvent: emitEvent, @@ -1608,6 +1640,9 @@ class FormGroup extends FormControlCollection> { findControlInCollection(path.split(_controlNameDelimiter)); } +typedef FormArrayControlFactory = AbstractControl Function( + int index, T? value); + /// A FormArray aggregates the values of each child FormControl into an array. /// /// It calculates its status by reducing the status values of its children. @@ -1619,6 +1654,17 @@ class FormGroup extends FormControlCollection> { class FormArray extends FormControlCollection> { final List> _controls = []; + /// A function that maps a value to a control. + /// Used in [updateValue] to create a new control for the value. + final FormArrayControlFactory? controlFactory; + + static AbstractControl defaultControlFactory(int index, T? value) { + return FormControl(value: value); + } + + FormArrayControlFactory get controlFactoryOrDefault => + controlFactory ?? defaultControlFactory; + /// Creates a new [FormArray] instance. /// /// When instantiating a [FormGroup], pass in a collection of child controls @@ -1660,6 +1706,7 @@ class FormArray extends FormControlCollection> { super.asyncValidators, super.asyncValidatorsDebounceTime, bool disabled = false, + this.controlFactory, }) : super( disabled: disabled, ) { @@ -2091,12 +2138,13 @@ class FormArray extends FormControlCollection> { } if (value != null && value.length > _controls.length) { + final controlFactory = controlFactoryOrDefault; final newControls = value .toList() .asMap() .entries .where((entry) => entry.key >= _controls.length) - .map((entry) => FormControl(value: entry.value)) + .map((entry) => controlFactory(entry.key, entry.value)) .toList(); addAll( diff --git a/test/src/models/form_array_test.dart b/test/src/models/form_array_test.dart index d081ce1b..5fc4e309 100644 --- a/test/src/models/form_array_test.dart +++ b/test/src/models/form_array_test.dart @@ -735,6 +735,51 @@ void main() { // Then: the status of the array is PENDING as well. expect(array.pending, true); }); + + test( + 'Test controlFactory', + () { + // Given: a FormArray with a controlFactory + + final array = FormArray>( + [ + FormControl(value: {}), + FormGroup({ + 'key': FormControl(), + }), + ], + controlFactory: (index, value) { + return FormGroup( + {}, + controlFactory: (key, value) => + FormControl(value: value as bool?), + )..value = value; + }, + ); + + // When: calling updateValue + array.updateValue([ + {'key': 'value1'}, + {'key': 20}, + {'key': true}, + ]); + + // Then: missing controls are created + expect(array.controls.length, 3); + expect( + (array.control('0').value as Map)['key'], + 'value1', + ); + expect( + array.control('1.key').value, + 20, + ); + expect( + array.control('2.key').value, + true, + ); + }, + ); }); } From 84af43b50271284fb0d3ebb634aad9ac4c78f9d5 Mon Sep 17 00:00:00 2001 From: ahmednfwela Date: Mon, 3 Mar 2025 11:09:56 +0200 Subject: [PATCH 2/3] updated readme and tests --- README.md | 184 ++++++++++++++++++--------- test/src/models/form_array_test.dart | 1 - 2 files changed, 125 insertions(+), 60 deletions(-) diff --git a/README.md b/README.md index caa84537..4acca935 100644 --- a/README.md +++ b/README.md @@ -6,55 +6,70 @@ This is a model-driven approach to handling Forms inputs and validations, heavil ## Table of Contents -- [Getting Started](#getting-started) +- [Reactive Forms](#reactive-forms) + - [Table of Contents](#table-of-contents) + - [Getting Started](#getting-started) - [Minimum Requirements](#minimum-requirements) - [Installation and Usage](#installation-and-usage) -- [Creating a form](#creating-a-form) -- [How to get/set Form data](#how-to-getset-form-data) -- [Validators](#what-about-validators) - - [Predefined validators](#predefined-validators) - - [Custom Validators](#custom-validators) - - [Pattern Validator](#pattern-validator) - - [FormGroup validators](#formgroup-validators) - - [Password and Password Confirmation](#what-about-password-and-password-confirmation) - - [Asynchronous Validators](#asynchronous-validators-sunglasses) - - [Debounce time in async validators](#debounce-time-in-async-validators) + - [Creating a form](#creating-a-form) + - [Default Values](#default-values) + - [How to get/set Form data](#how-to-getset-form-data) + - [What about Validators?](#what-about-validators) + - [Predefined validators](#predefined-validators) + - [FormControl](#formcontrol) + - [FormGroup](#formgroup) + - [FormArray](#formarray) + - [Custom Validators](#custom-validators) + - [Inheriting from `Validator` class:](#inheriting-from-validator-class) + - [Using the `Validators.delegate()` validator:](#using-the-validatorsdelegate-validator) + - [Pattern Validator](#pattern-validator) + - [FormGroup validators](#formgroup-validators) + - [What about Password and Password Confirmation?](#what-about-password-and-password-confirmation) + - [Asynchronous Validators :sunglasses:](#asynchronous-validators-sunglasses) + - [Debounce time in async validators](#debounce-time-in-async-validators) - [Composing Validators](#composing-validators) -- [Groups of Groups](#groups-of-groups-grin) -- [Dynamic forms with FormArray](#dynamic-forms-with-formarray) -- [Arrays of Groups](#arrays-of-groups) -- [FormBuilder](#formbuilder) - - [Groups](#groups) - - [Arrays](#arrays) - - [Control](#control) - - [Control state](#control-state) -- [Reactive Form Widgets](#reactive-form-widgets) -- [How to customize error messages?](#how-to-customize-error-messages) - - [Reactive Widget level](#1-reactive-widget-level) - - [Global/Application level](#2-globalapplication-level) - - [Parameterized validation messages](#parameterized-validation-messages) -- [When does Validation Messages begin to show up?](#when-does-validation-messages-begin-to-show-up) - - [Touching a control](#touching-a-control) - - [Overriding Reactive Widgets show errors behavior](#overriding-reactive-widgets-show-errors-behavior) -- [Enable/Disable Submit button](#enabledisable-submit-button) - - [Submit Button in a different Widget](#separating-submit-button-in-a-different-widget) - - [ReactiveFormConsumer widget](#using-reactiveformconsumer-widget) -- [Focus/UnFocus a FormControl](#focusunfocus-a-formcontrol) -- [Focus flow between Text Fields](#focus-flow-between-text-fields) -- [Enable/Disable a widget](#how-enabledisable-a-widget) -- [How does ReactiveTextField differs from native TextFormField or TextField?](#how-does-reactivetextfield-differs-from-native-textformfieldhttpsapiflutterdevfluttermaterialtextformfield-classhtml-or-textfieldhttpsapiflutterdevfluttermaterialtextfield-classhtml) -- [Reactive Form Field Widgets](#supported-reactive-form-field-widgets) -- [Bonus Field Widgets](#bonus-field-widgets) -- [Other Reactive Forms Widgets](#other-reactive-forms-widgets) -- [Advanced Reactive Field Widgets](#advanced-reactive-field-widgets) -- [ReactiveValueListenableBuilder to listen when value changes in a FormControl](#reactivevaluelistenablebuilder-to-listen-when-value-changes-in-a-formcontrol) -- [ReactiveForm vs ReactiveFormBuilder which one?](#reactiveform-vs-reactiveformbuilder-which-one) -- [Reactive Forms + Provider plugin](#reactive-forms--providerhttpspubdevpackagesprovider-plugin-muscle) -- [Reactive Forms + code generation plugin](#reactive-forms--code-generationhttpspubdevpackagesreactive_forms_generator-) -- [How create a custom Reactive Widget?](#how-create-a-custom-reactive-widget) -- [What is not Reactive Forms](#what-is-not-reactive-forms) -- [What is Reactive Forms](#what-is-reactive-forms) -- [Migrate versions](#migrate-versions) + - [Groups of Groups :grin:](#groups-of-groups-grin) + - [Dynamic forms with **FormArray**](#dynamic-forms-with-formarray) + - [Arrays of Groups](#arrays-of-groups) + - [FormBuilder](#formbuilder) + - [Groups](#groups) + - [Arrays](#arrays) + - [Control](#control) + - [Control state](#control-state) + - [Nested Controls](#nested-controls) + - [Reactive Form Widgets](#reactive-form-widgets) + - [How to customize error messages?](#how-to-customize-error-messages) + - [1. Reactive Widget level.](#1-reactive-widget-level) + - [2. Global/Application level.](#2-globalapplication-level) + - [Validation messages with error arguments:](#validation-messages-with-error-arguments) + - [Parameterized validation messages](#parameterized-validation-messages) + - [When does Validation Messages begin to show up?](#when-does-validation-messages-begin-to-show-up) + - [Touching a control](#touching-a-control) + - [Overriding Reactive Widgets show errors behavior](#overriding-reactive-widgets-show-errors-behavior) + - [Enable/Disable Submit button](#enabledisable-submit-button) + - [Separating Submit Button in a different Widget:](#separating-submit-button-in-a-different-widget) + - [Using **ReactiveFormConsumer** widget:](#using-reactiveformconsumer-widget) + - [Focus/UnFocus a **FormControl**](#focusunfocus-a-formcontrol) + - [Focus flow between Text Fields](#focus-flow-between-text-fields) + - [How Enable/Disable a widget](#how-enabledisable-a-widget) + - [How does **ReactiveTextField** differs from native TextFormField or TextField?](#how-does-reactivetextfield-differs-from-native-textformfield-or-textfield) + - [Supported Reactive Form Field Widgets](#supported-reactive-form-field-widgets) + - [Bonus Field Widgets](#bonus-field-widgets) + - [Other Reactive Forms Widgets](#other-reactive-forms-widgets) + - [Advanced Reactive Field Widgets](#advanced-reactive-field-widgets) + - [ReactiveTextField](#reactivetextfield) + - [ReactiveDropdownField](#reactivedropdownfield) + - [**ReactiveValueListenableBuilder** to listen when value changes in a **FormControl**](#reactivevaluelistenablebuilder-to-listen-when-value-changes-in-a-formcontrol) + - [**ReactiveForm** vs **ReactiveFormBuilder** which one?](#reactiveform-vs-reactiveformbuilder-which-one) + - [Widget testing](#widget-testing) + - [example component](#example-component) + - [example test](#example-test) + - [Reactive Forms + Provider plugin :muscle:](#reactive-forms--provider-plugin-muscle) + - [Reactive Forms + code generation 🤖](#reactive-forms--code-generation-) + - [How create a custom Reactive Widget?](#how-create-a-custom-reactive-widget) + - [What is not **Reactive Forms**](#what-is-not-reactive-forms) + - [What is **Reactive Forms**](#what-is-reactive-forms) + - [Migrate versions](#migrate-versions) ## Getting Started @@ -718,32 +733,73 @@ Map emptyAddressee(AbstractControl control) { } ``` +You can also pass `controlFactory` to `FormArray` and `FormGroup` to have complete control over the mapping: + +```dart +// Given: an empty array of strings +final array = FormArray( + [], + controlFactory: (int index, String? value) => FormControl( + value: value, + validators: [const RequiredValidator()], + ), +); + +// When: set value to array +array.value = ["john@email.com", "susan@email.com", "caroline@email.com"]; + +// Then: the array is no longer empty +expect(array.controls.length, 3); + +// And: the new controls are all required! +expect(array.controls('0').validators.length, 1); +expect(array.controls('1').validators.length, 1); +expect(array.controls('2').validators.length, 1); +``` + ## Arrays of Groups You can also create arrays of groups: ```dart // an array of groups -final addressArray = FormArray([ - FormGroup({ - 'city': FormControl(value: 'Sofia'), - 'zipCode': FormControl(value: 1000), - }), - FormGroup({ - 'city': FormControl(value: 'Havana'), - 'zipCode': FormControl(value: 10400), - }), -]); +final addressArray = FormArray( + [ + FormGroup({ + 'city': FormControl(value: 'Sofia'), + 'zipCode': FormControl(value: 1000), + }), + FormGroup({ + 'city': FormControl(value: 'Havana'), + 'zipCode': FormControl(value: 10400), + }), + ], + controlFactory: (index, value) { + return FormGroup({ + 'city': FormControl(), + 'zipCode': FormControl(), + }) + ..value = value; + }, +); ``` Another example using **FormBuilder**: ```dart // an array of groups using FormBuilder -final addressArray = fb.array([ +final addressArray = fb.array>([ fb.group({'city': 'Sofia', 'zipCode': 1000}), fb.group({'city': 'Havana', 'zipCode': 10400}), -]); +], +[], +[], +(index, value) { + return fb.group({ + 'city': FormControl(), + 'zipCode': FormControl(), + })..value = value as Map?; +}); ``` or just: @@ -753,7 +809,15 @@ or just: final addressArray = fb.array([ {'city': 'Sofia', 'zipCode': 1000}, {'city': 'Havana', 'zipCode': 10400}, -]); +], +[], +[], +(index, value) { + return fb.group({ + 'city': FormControl(), + 'zipCode': FormControl(), + })..value = value as Map?; +}); ``` You can iterate over groups as follow: @@ -768,6 +832,8 @@ final cities = addressArray.controls > A common mistake is to declare an _array_ of groups as _FormArray<FormGroup>_. > An array of _FormGroup_ must be declared as **FormArray()** or as **FormArray<Map<String, dynamic>>()**. +> It's mandatory to use `controlFactory` here if you intend to set the `FormArray.value` to add new elements, as the default behavior would create a `FormControl>` instead of `FormGroup` (see [#366](https://github.com/joanpablo/reactive_forms/issues/366)). + ## FormBuilder The **FormBuilder** provides syntactic sugar that shortens creating instances of a FormGroup, FormArray and FormControl. It reduces the amount of boilerplate needed to build complex forms. diff --git a/test/src/models/form_array_test.dart b/test/src/models/form_array_test.dart index 5fc4e309..7901a7cc 100644 --- a/test/src/models/form_array_test.dart +++ b/test/src/models/form_array_test.dart @@ -740,7 +740,6 @@ void main() { 'Test controlFactory', () { // Given: a FormArray with a controlFactory - final array = FormArray>( [ FormControl(value: {}), From b32d6b6f8f55869bec0b56c9e5c48063c65730f4 Mon Sep 17 00:00:00 2001 From: ahmednfwela Date: Mon, 3 Mar 2025 11:13:06 +0200 Subject: [PATCH 3/3] fixed readme formatting --- README.md | 129 +++++++++++++++++++++++++++--------------------------- 1 file changed, 64 insertions(+), 65 deletions(-) diff --git a/README.md b/README.md index 4acca935..45a53d38 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Reactive Forms +# Reactive Forms This is a model-driven approach to handling Forms inputs and validations, heavily inspired in [Angular's Reactive Forms](https://angular.io/guide/reactive-forms). @@ -6,70 +6,69 @@ This is a model-driven approach to handling Forms inputs and validations, heavil ## Table of Contents -- [Reactive Forms](#reactive-forms) - - [Table of Contents](#table-of-contents) - - [Getting Started](#getting-started) - - [Minimum Requirements](#minimum-requirements) - - [Installation and Usage](#installation-and-usage) - - [Creating a form](#creating-a-form) - - [Default Values](#default-values) - - [How to get/set Form data](#how-to-getset-form-data) - - [What about Validators?](#what-about-validators) - - [Predefined validators](#predefined-validators) - - [FormControl](#formcontrol) - - [FormGroup](#formgroup) - - [FormArray](#formarray) - - [Custom Validators](#custom-validators) - - [Inheriting from `Validator` class:](#inheriting-from-validator-class) - - [Using the `Validators.delegate()` validator:](#using-the-validatorsdelegate-validator) - - [Pattern Validator](#pattern-validator) - - [FormGroup validators](#formgroup-validators) - - [What about Password and Password Confirmation?](#what-about-password-and-password-confirmation) - - [Asynchronous Validators :sunglasses:](#asynchronous-validators-sunglasses) - - [Debounce time in async validators](#debounce-time-in-async-validators) - - [Composing Validators](#composing-validators) - - [Groups of Groups :grin:](#groups-of-groups-grin) - - [Dynamic forms with **FormArray**](#dynamic-forms-with-formarray) - - [Arrays of Groups](#arrays-of-groups) - - [FormBuilder](#formbuilder) - - [Groups](#groups) - - [Arrays](#arrays) - - [Control](#control) - - [Control state](#control-state) - - [Nested Controls](#nested-controls) - - [Reactive Form Widgets](#reactive-form-widgets) - - [How to customize error messages?](#how-to-customize-error-messages) - - [1. Reactive Widget level.](#1-reactive-widget-level) - - [2. Global/Application level.](#2-globalapplication-level) - - [Validation messages with error arguments:](#validation-messages-with-error-arguments) - - [Parameterized validation messages](#parameterized-validation-messages) - - [When does Validation Messages begin to show up?](#when-does-validation-messages-begin-to-show-up) - - [Touching a control](#touching-a-control) - - [Overriding Reactive Widgets show errors behavior](#overriding-reactive-widgets-show-errors-behavior) - - [Enable/Disable Submit button](#enabledisable-submit-button) - - [Separating Submit Button in a different Widget:](#separating-submit-button-in-a-different-widget) - - [Using **ReactiveFormConsumer** widget:](#using-reactiveformconsumer-widget) - - [Focus/UnFocus a **FormControl**](#focusunfocus-a-formcontrol) - - [Focus flow between Text Fields](#focus-flow-between-text-fields) - - [How Enable/Disable a widget](#how-enabledisable-a-widget) - - [How does **ReactiveTextField** differs from native TextFormField or TextField?](#how-does-reactivetextfield-differs-from-native-textformfield-or-textfield) - - [Supported Reactive Form Field Widgets](#supported-reactive-form-field-widgets) - - [Bonus Field Widgets](#bonus-field-widgets) - - [Other Reactive Forms Widgets](#other-reactive-forms-widgets) - - [Advanced Reactive Field Widgets](#advanced-reactive-field-widgets) - - [ReactiveTextField](#reactivetextfield) - - [ReactiveDropdownField](#reactivedropdownfield) - - [**ReactiveValueListenableBuilder** to listen when value changes in a **FormControl**](#reactivevaluelistenablebuilder-to-listen-when-value-changes-in-a-formcontrol) - - [**ReactiveForm** vs **ReactiveFormBuilder** which one?](#reactiveform-vs-reactiveformbuilder-which-one) - - [Widget testing](#widget-testing) - - [example component](#example-component) - - [example test](#example-test) - - [Reactive Forms + Provider plugin :muscle:](#reactive-forms--provider-plugin-muscle) - - [Reactive Forms + code generation 🤖](#reactive-forms--code-generation-) - - [How create a custom Reactive Widget?](#how-create-a-custom-reactive-widget) - - [What is not **Reactive Forms**](#what-is-not-reactive-forms) - - [What is **Reactive Forms**](#what-is-reactive-forms) - - [Migrate versions](#migrate-versions) +- [Table of Contents](#table-of-contents) +- [Getting Started](#getting-started) +- [Minimum Requirements](#minimum-requirements) +- [Installation and Usage](#installation-and-usage) +- [Creating a form](#creating-a-form) +- [Default Values](#default-values) +- [How to get/set Form data](#how-to-getset-form-data) +- [What about Validators?](#what-about-validators) + - [Predefined validators](#predefined-validators) + - [FormControl](#formcontrol) + - [FormGroup](#formgroup) + - [FormArray](#formarray) + - [Custom Validators](#custom-validators) + - [Inheriting from `Validator` class:](#inheriting-from-validator-class) + - [Using the `Validators.delegate()` validator:](#using-the-validatorsdelegate-validator) + - [Pattern Validator](#pattern-validator) + - [FormGroup validators](#formgroup-validators) +- [What about Password and Password Confirmation?](#what-about-password-and-password-confirmation) +- [Asynchronous Validators :sunglasses:](#asynchronous-validators-sunglasses) + - [Debounce time in async validators](#debounce-time-in-async-validators) +- [Composing Validators](#composing-validators) +- [Groups of Groups :grin:](#groups-of-groups-grin) +- [Dynamic forms with **FormArray**](#dynamic-forms-with-formarray) +- [Arrays of Groups](#arrays-of-groups) +- [FormBuilder](#formbuilder) + - [Groups](#groups) + - [Arrays](#arrays) + - [Control](#control) + - [Control state](#control-state) +- [Nested Controls](#nested-controls) +- [Reactive Form Widgets](#reactive-form-widgets) +- [How to customize error messages?](#how-to-customize-error-messages) + - [1. Reactive Widget level.](#1-reactive-widget-level) + - [2. Global/Application level.](#2-globalapplication-level) + - [Validation messages with error arguments:](#validation-messages-with-error-arguments) + - [Parameterized validation messages](#parameterized-validation-messages) +- [When does Validation Messages begin to show up?](#when-does-validation-messages-begin-to-show-up) + - [Touching a control](#touching-a-control) + - [Overriding Reactive Widgets show errors behavior](#overriding-reactive-widgets-show-errors-behavior) +- [Enable/Disable Submit button](#enabledisable-submit-button) + - [Separating Submit Button in a different Widget:](#separating-submit-button-in-a-different-widget) + - [Using **ReactiveFormConsumer** widget:](#using-reactiveformconsumer-widget) +- [Focus/UnFocus a **FormControl**](#focusunfocus-a-formcontrol) +- [Focus flow between Text Fields](#focus-flow-between-text-fields) +- [How Enable/Disable a widget](#how-enabledisable-a-widget) +- [How does **ReactiveTextField** differs from native TextFormField or TextField?](#how-does-reactivetextfield-differs-from-native-textformfield-or-textfield) +- [Supported Reactive Form Field Widgets](#supported-reactive-form-field-widgets) +- [Bonus Field Widgets](#bonus-field-widgets) +- [Other Reactive Forms Widgets](#other-reactive-forms-widgets) +- [Advanced Reactive Field Widgets](#advanced-reactive-field-widgets) + - [ReactiveTextField](#reactivetextfield) + - [ReactiveDropdownField](#reactivedropdownfield) +- [**ReactiveValueListenableBuilder** to listen when value changes in a **FormControl**](#reactivevaluelistenablebuilder-to-listen-when-value-changes-in-a-formcontrol) +- [**ReactiveForm** vs **ReactiveFormBuilder** which one?](#reactiveform-vs-reactiveformbuilder-which-one) +- [Widget testing](#widget-testing) + - [example component](#example-component) + - [example test](#example-test) +- [Reactive Forms + Provider plugin :muscle:](#reactive-forms--provider-plugin-muscle) +- [Reactive Forms + code generation 🤖](#reactive-forms--code-generation-) +- [How create a custom Reactive Widget?](#how-create-a-custom-reactive-widget) +- [What is not **Reactive Forms**](#what-is-not-reactive-forms) +- [What is **Reactive Forms**](#what-is-reactive-forms) +- [Migrate versions](#migrate-versions) ## Getting Started