joanpablo / reactive_forms

This is a model-driven approach to handling form inputs and validations, heavily inspired in Angular's Reactive Forms
MIT License
469 stars 89 forks source link

Need to force AsyncValidator to run even if control.status is invalid #391

Closed BenjiFarquhar closed 1 year ago

BenjiFarquhar commented 1 year ago

I need a way to bypass this if check in updateValueAndValidity: if (_status == ControlStatus.valid || _status == ControlStatus.pending) {

The status is invalid, but I need to run the validator, because the validator marks another field as valid. Since the validator doesn't run, the other field remains invalid. Something like a forceValidate argument in updateValueAndValidity will do.

  void updateValueAndValidity({
    bool updateParent = true,
    bool emitEvent = true,
  }) {
    _setInitialStatus();
    _updateValue();
    if (enabled) {
      _cancelExistingSubscription();
      _errors = _runValidators();
      _status = _calculateStatus();
      if (_status == ControlStatus.valid || _status == ControlStatus.pending) {
        _runAsyncValidators();
      }
    }

    if (emitEvent) {
      _valueChanges.add(value);
      _statusChanges.add(_status);
    }

    _updateAncestors(updateParent);
  }
BenjiFarquhar commented 1 year ago

I've done this, I think it is working, but the pending state doesn't seem to show the circularProgressIndicator is all.

            ((formModel as dynamic).companyNameControl as FormControl)
                .asyncValidators[0]
                .call((formModel as dynamic).companyNameControl as FormControl);
vasilich6107 commented 1 year ago

I need to run the validator, because the validator marks another field as valid

This sound like very strange approach)

BenjiFarquhar commented 1 year ago

Actually I have managed this with my comment code snippet above.

vasilich6107 commented 1 year ago

Sure. It's nice that it works for you. It was the general recommendation that validator should not toggle the field. I do not know your use case but this is not a validators job

BenjiFarquhar commented 1 year ago

@vasilich6107 Thanks for the tip. In my use case, editing field 2 needs to trigger re-validation of field 1, because an item must be unique on two fields. I could trigger it from the onChanged method of the text field instead, is that probably better practice?

vasilich6107 commented 1 year ago

It's better to validate on group level for uniqueness

vasilich6107 commented 1 year ago

https://github.com/artflutter/reactive_forms_generator/blob/master/packages/reactive_forms_generator/example/lib/docs/mailing_list/mailing_list.dart

Here is how to validate unique emails. They are validated on the closest common control - array in this case.

BenjiFarquhar commented 1 year ago

@vasilich6107 Thanks. I need mine to make an HTTP request to check uniqueness so validating on the group level won't be performant (not just unique to another form control).

I see you've made a lot of updates. Can we use async validators in the form now without resorting to (formModel as dynamic).nameControl!.setAsyncValidators(? My reactive_forms_generator version is old, but I would like to update it to the latest, especially to figure out what the new formModel.addEmailListItem('') in the docs can do.

However, ReactiveTouchSpin only works with reactive_forms 14. I have commented out reactive_touch_spin for now but will need to add it back as soon as possible.

I assume we need to update all our validators to be a Validator class instance, not aMap<String, dynamic>? Function(AbstractControl control) function?

vasilich6107 commented 1 year ago

won't be performant

You can extract several of your controls into separate formGroup to limit the async validator calls.

touch spin

it depends on intl 0.17.0 and not supported so we have not so many options

Can we use async validators

Yes

You can now even skip annotations for controls if they do not need any specific validations. All unannotated fields treated as controls.

BenjiFarquhar commented 1 year ago

@vasilich6107 Do you have an example of a nested form group? The error does not show up anymore. It is getting very messy. I think I have to override this from ReactiveFormField to look at the parent control instead of the control:

  String? get errorText {
    if (control.hasErrors && _showErrors) {
      final errorKey = control.errors.keys.first;
      final validationMessage = _findValidationMessage(errorKey);

      return validationMessage != null
          ? validationMessage(control.getError(errorKey)!)
          : errorKey;
    }

    return null;
  }

It also seems to define the child Form in both the child and parent generated files.

Also the validator runs when we edit the child field, but the other child field does not re-evaluate if it should show the error, unless you edit it directly.

BenjiFarquhar commented 1 year ago

@vasilich6107 I gave up on the nested form, it seems buggy. My async validator also can't be used in a generator because it isn't a const. I have upgraded to latest though. I will fork the TouchSpin and update intl now, i will let you know if it is a tiny PR worth doing for the reactive_forms_touchspin.

BenjiFarquhar commented 1 year ago

@vasilich6107 I have forked and updated intl in flutter_touch_spin (did a PR but I doubt it will be accepted). I see the reactive_touch_spin is removed from reactive_forms_widgets so I just grabbed it from my local .pubcache and am hosting it locally on my machine for now, let me know if you would prefer something else since it is maintained by you.

vasilich6107 commented 1 year ago

Hi @BenjiFarquhar I'm have no plans to any third party package by myself.

You can publish touchspin2 on pub dev and then we can switch reactive_touch_spin to updated version

BenjiFarquhar commented 1 year ago

Hi @vasilich6107. I have published flutter_touch_spin_2. Will you update reactive_touch_spin to use it? Cheers.

BenjiFarquhar commented 8 months ago

@vasilich6107 Have you ever displayed an error from a whole formGroup validator? I think we can use ReactiveFormControlStatusBuilder passing in the whole form as the control. But how do we access the error text from a control, not a field?

vasilich6107 commented 8 months ago

hi @BenjiFarquhar Each item of RF nested object structure is AbstractControl with errors field https://github.com/joanpablo/reactive_forms/blob/master/lib/src/models/models.dart#L194