-
Notifications
You must be signed in to change notification settings - Fork 110
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Form with a dynamic array of objects inside plus validation #45
Comments
For 1. (initializing the state based on event information) and 3. (the heat being required or not) there are a couple of options. If you can model your state so that the form state is a child of the state that contains the required event information then you can provide the additional information as parameters (see the second code example here for how this could be done). However, I would recommend that you create a new action that contains all the required information. Then you can handle that action inside the reducer that contains the form state to initialize it properly. The initial player array can simply be set via Regarding the validation, you would simply create a three-level nested update function, i.e. an PS: I am specifically not providing any code examples to try to enable you to figure this out by yourself. However, if you want more concrete examples let me know and I'll write the code for you. Just be aware that it is always better to write the code yourself :) |
Thank you :-). I'll go through all the info you have supplied and try to figure it out but I have tried unsuccessfully twice to get this working. The first time was about 2 months ago and I gave up after about 2 days and went back to using Angular reactive forms and the second time was yesterday when I tried for about 2 hours before writing the above question. I'm pretty new to Angular which is probably most of the problem :-) |
Alright, I've typed up some code that does what you need. Feel free to not look at it yet and try a bit more or ask questions. However, I understand that it is sometimes easier to learn from examples than to write everything yourself. import { Action } from '@ngrx/store';
import {
createFormGroupState,
FormGroupState,
updateGroup,
validate,
updateArray,
formGroupReducer,
setUserDefinedProperty,
addArrayControl,
cast,
} from 'ngrx-forms';
import { required, pattern, greaterThan } from 'ngrx-forms/validation';
export interface PlayerFormValue {
ebuNumber: string;
firstName: string;
lastName: string
}
export interface EventFormValue {
comments: string;
heat: number;
players: PlayerFormValue[]
}
export class InitializeEventFormAction implements Action {
static readonly TYPE = 'my-actions/INITIALIZE_EVENT_FORM';
readonly type = InitializeEventFormAction.TYPE;
constructor(
public heatIsRequired: boolean,
public initialPlayers: PlayerFormValue[],
) { }
}
export class AddPlayerAction implements Action {
static readonly TYPE = 'my-actions/ADD_PLAYER';
readonly type = AddPlayerAction.TYPE;
constructor(
public newPlayer: PlayerFormValue,
) { }
}
type EventFormActions =
| InitializeEventFormAction
| AddPlayerAction
;
const HEAT_IS_REQUIRED_PROPERTY = 'heatIsRequired';
const EVENT_FORM_ID = 'EVENT_FORM';
const INIITIAL_STATE = createFormGroupState<EventFormValue>(EVENT_FORM_ID, {
comments: '',
heat: 0,
players: [],
});
export function eventFormReducer(state = INIITIAL_STATE, action: EventFormActions) {
const validateForm = updateGroup<EventFormValue>({
heat: heat => heat.userDefinedProperties[HEAT_IS_REQUIRED_PROPERTY] ? validate([required, greaterThan(0)], heat) : heat,
players: updateArray(
updateGroup<PlayerFormValue>({
ebuNumber: validate<string>([required, pattern(/^[0-9a-z]+$/)]),
firstName: validate<string>(required),
lastName: validate<string>(required),
})
),
});
switch (action.type) {
case InitializeEventFormAction.TYPE:
state = createFormGroupState<EventFormValue>(EVENT_FORM_ID, {
comments: '',
heat: 0,
players: action.initialPlayers,
});
state = setUserDefinedProperty(HEAT_IS_REQUIRED_PROPERTY, action.heatIsRequired, state);
break;
case AddPlayerAction.TYPE:
state = updateGroup<EventFormValue>(state, {
players: players => addArrayControl<PlayerFormValue>(action.newPlayer, cast(players)),
});
break;
default:
state = formGroupReducer(state, action);
}
return validateForm(state);
} |
Thank you so much :-). I really appreciate you taking the time out to help me like this. |
Another question (sorry :-)) - How would I go about updating the players array so that only the player referenced by a given index number is updated in the reducer? I have found the 'updateArray' function, but that seems to apply the given function to all elements of the array. |
That is indeed something that is still missing from the API. However, it is pretty easy to add this yourself. Have a look at this issue for the required code. |
Cool - thanks. One last question - is it possible to have userDefinedFields on each of the 'player' objects inside the array? |
Sure, just replace the players update with something like this: players: updateArray(
state => {
state = setUserDefinedProperty('yourProperty', someValue, state);
return updateGroup<PlayerFormValue>(state, {
ebuNumber: validate<string>([required, pattern(/^[0-9a-z]+$/)]),
firstName: validate<string>(required),
lastName: validate<string>(required),
});
}
), |
Brill thanks - everything nearly working now! I just noticed a small issue though - on my form I have a 'ebuNumber' field and next to it a 'find player' button. The idea is that the person enters the ebu number, clicks the button and the first name/last name get loaded into two other fields. It seems that what is happening is that when I click the button, the 'onBlur' event fires ok and the ebu number is updated in the form state (because I have [ngrxUpdateOn]="'blur'") but the 'Find player' action on the button is not fired. If I click the button again, the 'Find player' action is then fired. If I remove the [ngrxUpdateOn] from the field, every time a character is entered the control loses its focus and I have to click on it again to enter another character. Any ideas on what I might be doing wrong? |
Also, I'm not sure where I should put the code you wrote above? I would like it to be part of the form initialization process: I have the following at the moment:
|
Regarding your second question you can simply perform an let newState = createFormGroupState<EntryFormValue>(ENTRY_FORM_ID, {
players: initializePlayers(playerCount(action.payload.event)),
});
newState = updateGroup<EventFormValue>(newState, {
players: updateArray(setUserDefinedProperty('yourProperty', someValue)),
}); Regarding the first issue, do you by chance use |
Brilliant - thank you :-) |
@jacqui932 would you be kind enough to share some of the html you used to display this form? I'm having an issue where when I change an array item value control value it loses focus. Must do something with my ngFor loop. |
No problem. In the HTML:
In the component class:
|
Thanks, I was using |
@patricknazar you shouldn't need to use update on "blur" for that unfocusing behaviour to go away. In general I recommend not using "blur" since it defers all validation etc to the blur event as well. |
@MrWolfZ You're right. It was again an issue with my trackBy: typo!! Works a treat now thanks! |
Hi,
I have a model which is something like this:
Player {
ebuNumber: number;
firstName: string;
lastName: string
}
EntryForm{
comments: string;
heat: number;
players: Player[]
}
How would I go about creating the ngrx form to model this? I also need to be able to do the following:
Dynamically add players to the players array - also default the size of this when the form is initially created to a known number of players which depends on the event they are entering (stored in another part of the store)
Perform validation on each of the players - i.e., ebuNumber must be of a particular format and firstName/lastName are required fields for all players
The heat is a required field based on the value which is stored in another part of the store - i..e, the event they are entering may or not have associated heats and only if the event has heats is the heat id mandatory
Any help you could give would be very much appreciated. :-)
The text was updated successfully, but these errors were encountered: