MrWolfZ / ngrx-forms

Enhance your forms in Angular applications with the power of ngrx

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Programmatically Triggering Async Validation

littleraeofsun opened this issue · comments

I have a form with draft feature where users can save their progress and resume it at a later date. One of our requirements is to display all validation messages when a draft is resumed so users can see which fields need attention. The bulk of our validation is done in effects to leverage back-end validators so as to prevent duplicating code.

Do you have any suggestions for programmatically triggering async validators for an entire form without requiring the user to touch all the fields?

The best solution I can think of so far is manually calling a SetValue action for every single field (as our validation effects are listening for that action), but that feels far from practical.

Hi there. If your effects are already listening for the SetValueAction to be triggered, couldn't you just create a new custom action and have all those effects listen to that same action in addition to the individual SetValueActions? Then you just dispatch that action whenever you want to trigger the validation.

Or alternatively create a new effect that is triggered by that custom action and emits all the SetValueActions?

IMO having each effect listen to an extra action is the most sustainable approach, because that way adding a new validation effect can be done atomically in that validation's effect, while the second option requires you to remember to always extend that proxy effect for each new validation that is added.

I hope this helps. Please re-open the issue if you have any more questions.

I might have mischaracterized our situation. We have one Validation endpoint that we call, essentially passing in a specific field name and its value, and it returns validation for that specific field. Each of our form fields are generically passed into this service for validation.

Then we have one effect that subscribes to the SetValueAction feed, parses out the field that is being changed, and passes the field name and value up to our server. It's fairly field-agnostic.

So I would have to create a separate action for every single field to trigger this effect at the same time, at which point I might as well trigger SetValueAction on every field.

Hmm, I see. From a performance and network traffic perspective I would suggest creating a new endpoint that takes the whole form value and then performs all the validations server-side in a single call.

However, if you want to re-use the existing API then I'd still create a new action to trigger the validation and then build a new effect that builds the transitive hull of the form components and calls the API for each field (either sequentially or in parallel).

I've been out of Angular and general JavaScript development for some time, so take the following with a grain of salt, but something like this:

  triggerValidation$: Observable<Action> = createEffect(() =>
    this.actions$.pipe(
      ofType(TriggerFormValidationAction.TYPE),
      mergeMap((_) => this.store.select((s) => s.formState)),
      map((formState) => {
        const getAllValues = (
          name: string,
          s: FormState<any>
        ): { name: string; value: unknown }[] =>
          isGroupState(s)
            ? Object.entries(s.controls).flatMap((a) => getAllValues(...a))
            : isArrayState(s)
            ? s.controls.flatMap((c, i) => getAllValues(`${name}.${i}`, c))
            : [{ name, value: s.value }];

        return {
          formState,
          namedValues: getAllValues("myForm", formState),
        };

      }),
      mergeMap(({ formState, namedValues }) => {
        // ... perform validation

        const formErrorActions = [new SetErrorsAction(formState.controls.field1.id, { serverError: 'example' })];
        return from(formErrorActions);
      })
    )
  );