MrWolfZ/ngrx-forms

Using Custom validations in forms.

anirudhr95 opened this issue · 3 comments

Hello, I came across a couple of Github issues which go over this topic - but I still face an issue in what I'm trying to do. I want a few fields in my form to be required depending on the value of other fields. (Conditionally validate i.e)

I am using:

"ngrx-forms": "4.1.0",
"@ngrx/effects": "^7.0.0",
"@ngrx/store": "^7.0.0",
"@ngrx/store-devtools": "^9.2.0",

I have a
FrameSpecForm.ts

import {createFormGroupState, FormGroupState, updateGroup, validate} from 'ngrx-forms';
import {orientation} from "./Orientation";
import {required} from "ngrx-forms/validation";
import {ProhibitionType} from "./ProhibitionType";

export interface FrameSpecForm {
  id: string,
  type: string,
  format: string,
  digitalMotionType:string,
  minAdDuration:number,
  maxAdDuration:number,
  shareOfTime:number,
  transitMedia:string,
  lattitude: number,
  longitude: number,
  address: string,
  zipCode: string,
  city: string,
  state: string,
  region:string
  notes: string
}

export const FrameSpecFormUniqueId = '3124';

export const frameSpecFormInitialState: FormGroupState<FrameSpecForm> = createFormGroupState(FrameSpecFormUniqueId, {
  id: '',
  type: '',
  format: '',
  digitalMotionType:'',
  minAdDuration:undefined,
  maxAdDuration:undefined,
  shareOfTime:undefined,
  transitMedia:'No',
  lattitude: undefined,
  longitude: undefined,
  address: '',
  zipCode: '',
  city: '',
  state: '',
  notes: '',
  region:'',
});

In my reducer, I am trying to validate this -

import {
  FormGroupState,
  formGroupReducer,
  Actions,
  updateGroup,
  validate,
  StateUpdateFns
} from 'ngrx-forms';
import { frameSpecFormInitialState, FrameSpecForm } from 'src/app/models/frame-spec-form';
import {filterValues} from "./FrameSpecFilterFetch.reducer";
import {required} from "ngrx-forms/validation";

interface AppState {
  frameStateSpec: FormGroupState<FrameSpecForm>;
}

const initialState: AppState = {
    frameStateSpec: frameSpecFormInitialState
};


const frameSpecFormValidation = updateGroup<FrameSpecForm>({
  id: validate(required),
  type: validate(required),
  format:validate(required),
  digitalMotionType: (digitalMotionType, formState) => {
    const isValidationRequired = formState.value.type === 'DIGITAL';
    const validationFunction =  isValidationRequired ? required : () => ({});
    return validate(validationFunction);
  }

  // This is the validation I want.
 // If I comment this out, everything works fine.
}


export function FrameSpecificationReducer(
         state: AppState = initialState,
         action: Actions<any>
       ): AppState {
        const frameStateSpec = frameSpecFormValidation(formGroupReducer(state.frameStateSpec, action));

        if (frameStateSpec !== state.frameStateSpec) {
          state.frameStateSpec = frameStateSpec;
        }


         switch (action.type) {

           default: return { ...state };
         }
}

This is the error I get

ERROR in src/app/create-frame-page/reducers/FrameSpecification.reducer.ts(33,3): error TS2345: Argument of type '{ id: (state: AbstractControlState<string>) => FormControlState<string>; type: (state: AbstractControlState<string>) => FormControlState<string>; format: (state: AbstractControlState<string>) => FormControlState<...>; digitalMotionType: (digitalMotionType: FormControlState<...>, formState: FormGroupState<...>) => (s...' is not assignable to parameter of type 'StateUpdateFns<FrameSpecForm>[]'.

Object literal may only specify known properties, and 'id' does not exist in type 'StateUpdateFns<FrameSpecForm>[]'.

Without the new validation:
image

With the new validation:
image

Note: If I instead use this

  digitalMotionType: (digitalMotionType, formState) => {
    const isValidationRequired = formState.value.type === 'DIGITAL';
    const validationFunction =  isValidationRequired ? required : () => ({});
    return validate(validationFunction, digitalMotionType);
  }

I get this error:

ERROR in src/app/create-frame-page/reducers/FrameSpecification.reducer.ts(39,21): error TS2345: Argument of type '(<T>(value: T | Boxed<T>) => ValidationErrors) | (() => {})' is not assignable to parameter of type 'AbstractControlState<{}>'.
  Type '<T>(value: T | Boxed<T>) => ValidationErrors' is missing the following properties from type 'AbstractControlState<{}>': id, value, isValid, isInvalid, and 12 more.

Edit #2: There is another clarification made by the author after I closed the post using this comment. You probably want to look at that.

Closing this issue.

The problem was this:

If you read this, please know it is out of date: #5

instead of returning an empty function, you should be returning the state. The latest documentation/clarification is given here by @MrWolfZ himself - #126

Therefore, the right way to code this is:

const frameSpecFormValidation = updateGroup<FrameSpecForm>({
  id: validate(required),
  type: validate(required),
  format: validate(required),
   digitalMotionType: (digitalMotionType, formState) => {
    const isValidationRequired = formState.value.type === 'Digital';
    return isValidationRequired ? validate(digitalMotionType, required) : digitalMotionType;
  }
});

TADA!

@anirudhr95 your original code was almost correct, it just needed a minor adjustment to work. You flipped the order of parameters for validate, i.e. this works:

  digitalMotionType: (digitalMotionType, formState) => {
    const isValidationRequired = formState.value.type === 'DIGITAL';
    const validationFunction = isValidationRequired ? required : () => ({});
    return validate(digitalMotionType, validationFunction);
  },

That said, both approach work, but do slightly different things. The first approach (i.e. using a validation function that returns {}) will clear all validation errors if the type is not DIGITAL, while the second version will leave any existing validation errors on the digitalMotionType control. Usually you would probably want the former.

@MrWolfZ Thanks for the clarification. My requirement is the former case as you guessed. I would have never figured out the issue myself if I went ahead thinking my code is working the way I wanted it to.

Also, thanks for your reply in such a short time.