Angular - Passing FormBuilder values to Array results in empty string values - data-structures

I have a form that I fill out using Angular's FormBuilder. This is the FormBuilder representation in the component typescript file:
constructor(private Form: FormBuilder) { }
AddStatusBoxForm = this.Form.group({
cpuTempTopic: [''],
uptimeTopic: [''],
timeTopic: [''],
});
I want to create a data structure that I can put these form values into, like so:
array: any[] = [this.AddStatusBoxForm.get('cpuTempTopic')?.value,
this.AddStatusBoxForm.get('uptimeTopic')?.value,
this.AddStatusBoxForm.get('timeTopic')?.value];
However console logging the array, results in empty strings. Yet if I console log the FormBuilder values on their own they print out fine:
console.log(this.AddStatusBoxForm.get('cpuTempTopic')?.value) // prints out fine
console.log(this.AddStatusBoxForm.get('uptimeTopic')?.value)
console.log(this.AddStatusBoxForm.get('timeTopic')?.value)
console.log(this.array) // prints out empty string
I can't figure out why this is the case, I can't seem to pass the FormBuilder values to any sort of data structure. I've also tried a Map and the same occurs where the passed in values seem to end up as empty strings in the data structure. Yet accessing the FormBuilder values on their own seems to work fine.
I've even tried to console log the type of the values from the FormBuilder which results in string:
console.log(typeof(this.AddStatusBoxForm.get('cpuTempTopic')?.value))
Any help with this would be appreciated.

This is because initially all the properties of the FormBuilder are initialized with empty string so while storing their values into an array will result with empty string values.
You need to subscribe to value changes of the FormGroup in order to update your array values in real time. You can refer the example below and bring modifications to your code accordingly.
addStatusBoxFormChangesSubscription: Subscription;
ngOnInit(): void {
this.addStatusBoxFormChangesSubscription = merge(
this.AddStatusBoxForm.get('cpuTempTopic').valueChanges,
this.AddStatusBoxForm.get('uptimeTopic').valueChanges,
this.AddStatusBoxForm.get('timeTopic').valueChanges
).subscribe(() => (this.array = [this.AddStatusBoxForm.get('cpuTempTopic')?.value,
this.AddStatusBoxForm.get('uptimeTopic')?.value,
this.AddStatusBoxForm.get('timeTopic')?.value];));
}
ngOnDestroy(): void {
this.addStatusBoxFormChangesSubscription?.unsubscribe();
}
It is always a good practice to unsubscribe from the subscription when not working on the same page anymore.
You can explore more about form group here FormGroup documentation.

Related

check if an array contains a string with Yup

I'm trying to make a field in a form required if my array contains a certain string.
For example, the field 'spouseName' should be required if the array familyMembers contains 'spouse'. Is it possible to use the .when() function to check the values in array? I'm using it to check the value of strings in other parts of the forms like this:
jobTitle: Yup.string().when("jobStatus", {
is: "employed",
then: Yup.string().required(requiredError)
})
is there a way to do something like:
spouseName: Yup.string().when("familyMembers", {
contains: "spouse",
then: Yup.string().required(requiredError)
})
Instead of passing the second parameter as a object, you could pass it as a function that receives the value of the field from the first parameter (in your case, the value of familyMembers) and the schema to be modified (in your case, add required).
You can see how to do this in the docs mixed.when (It's the last example).
e.g. from the docs
yup.object({
isBig: yup.boolean(),
count: yup.number().when('isBig', (isBig, schema) => {
return isBig ? schema.min(5) : schema.min(0);
}),
});
So in your case, it should be something like
spouseName: Yup.string().when("familyMembers", (familyMembers, schema) => {
// if it includes 'spouse'
return familyMembers.includes('spouse') ?
Yup.string().required(requiredError) : // add required validation
schema; // don't change anything
}),
})
You can do other logic inside the function and also return diferent schema types.

Nested Models with separate API calls and separate stores (using custom references)

I'm wondering what's the best practice to do two separate fetches to data that would belong to the same Model. One to get all Users data and a separate one that would request their Traits and add them to each User.
I think I could create a reference in User, to fill the data, but im not sure how to create the custom reference since it should be an array.
export const User = types
.model('User', {
id: types.identifierNumber,
...
traits: types.maybeNull(TraitsbyUserReference),
})
const TraitsbyUserReference = types.maybe(
types.reference(Trait, {
get(identifier: string, parent): {
return (parent as Instance<typeof TraitsStore>).getAllTraits()
},
set(value) {
return value; // this is what doesnt work out because i'm fetching a whole array
},
}),
)
Also, is this a good practice or are there other better ways of getting this result?
Thanks!
In terms of defining the model, you might try switching from maybeNull to an optional array with a default value in your model -
...
traits: types.optional(types.array(Trait), []),
...
As such, the model will always be instantiated with an empty traits collection.
In terms of the TraitsbyUserReference, I am not following what abstraction that you need with the dynamic store look-up. You could create an action (e.g. User.actions(self => ...)) to look-up the traits as a separate api -
getUserTraits(){
/* this will vary based on your implementation of TraitsStore and how it is injected */
const traits = self.TraitsStore.getAllTraits(self.id);
self.traits = traits;
}

adding fields to a redux form inside the component

So I have a React component (called Contract) which is being fed to the reduxForm function. It's been given an array of field names (the fields property shown below) which will be part of the form.
export default reduxForm({
form: 'Contract',
fields
}, mapStateToProps)(Contract);
But while this component is being created, there is data (additional field names) being loaded from a server which will need to be appended to this list. That data may not load in time. Is it possible to dynamically update the list of fields and make them part of the form after this component has been created? Can it be updated inside the Contract component or is it that once this component has been created the list of fields is set in stone?
I'm assuming you're using version 5 or earlier since you've got a fields config. You can make your fields dynamic by passing a fields prop to your form component instead of configuring them in reduxForm. (See docs)
I used a stateful component in this example to fetch the fields but you could also of course use a redux-connected component.
class ContractContainer extends Component {
state = { fields: [] };
componentDidMount() {
fetchData().then(fields => {
this.setState({ fields });
}
}
render() {
<Contract fields={[...staticFields, ...this.state.fields]} />
}
}

Dynamically generated form submission and Spring framework?

I have a dynamically generated form (using jQuery). So beforehand I don't know how many form elements do I have. Does anybody have any idea how we can do it.?
POST the fields in the request body as a JSON-ified map, and use Jackson to to turn it into a Map<String, String> on the controller side. Your controller method would look something like this:
#RequestMapping(value="/url", method=RequestMethod.POST)
public getMap(#RequestBody Map<String, String> fields){
//Manipulate fields as you wish
.
.
.
}
That way you will have your fields as key/pair values inside the map ready for you to manipulate them.
You can turn your fields into a map fairly easily using jQuery (or similar) like this:
var fields = {}
$.each("input:text", function(i, item){
fields[mapKeyValue] = $(item).val();
}
This example assumes that your pair value is the text input, but you can manipulate the values however you want. This is by no means a complete example, but should give you an idea on how to start.

Knockout Mapping - Fill Observable Arrays keeping Items' methods

I've been facing a problem that is basically the following:
I have a knockout ViewModel which contains observable arrays of items with observable properties and methods.
I need to pull data from the server. The methods need to exist after data is taken from server. So I create a new ViewModel and then update its value from what comes from server. (THIS DOES NOT WORK, THE RESULTING ARRAY HAS NO ITEMS)
If I create, with mapping, a new object using var newObj = ko.mapping.fromJS(data) the resulting Array has items, but its items have no methods. It spoils my Bindings.
The fiddle of my problem: http://jsfiddle.net/claykaboom/R823a/3/ ( It works util you click in "Load Data From The Server" )
The final question is: What is the best way to have items on the final array without making the loading process too cumbersome, such as iterating through every item and filling item's properties in order to keep the previously declared methods?
Thanks,
I changed your code little bit. Check this version of JSFiddle.
var jsonFromServer = '{"ModuleId":1,"Metadatas":[{"Id":1,"MinValue":null,"MaxValue":null,"FieldName":"Teste","SelectedType":"String","SelectedOptionType":null,"IsRequired":true,"Options":[]}]}';
Your code doesnt work because your jsonFromServer variable does not contain methods we need at binding like you described in your question. ( -- > Metadatas )
So we need to define a custom create function for Metadata objects at the mapping process like this :
var mapping = {
'Metadatas': {
create: function(options) {
var newMetaData = new MetadataViewModel(options.parent);
newMetaData.Id(options.data.id);
newMetaData.FieldName(options.data.FieldName);
newMetaData.SelectedType(options.data.SelectedType);
newMetaData.SelectedOptionType(options.data.SelectedOptionType);
newMetaData.IsRequired(options.data.IsRequired);
newMetaData.Options(options.data.Options);
// You can get current viewModel instance via options.parent
// console.log(options.parent);
return newMetaData;
}
}
}
Then i changed your load function to this :
self.LoadDataFromServer = function() {
var jsonFromServer = '{"ModuleId":1,"Metadatas":[{"Id":1,"MinValue":null,"MaxValue":null,"FieldName":"Teste","SelectedType":"String","SelectedOptionType":null,"IsRequired":true,"Options":[]}]}';
ko.mapping.fromJSON(jsonFromServer, mapping, self);
}
You dont have to declare a new viewModel and call ko.applyBindings again. Assigning the updated mapping to current viewModel is enough. For more information check this link. Look out for customizing object construction part.
The final question is: What is the best way to have items on the final
array without making the loading process too cumbersome, such as
iterating through every item and filling item's properties in order to
keep the previously declared methods?
As far as i know there is no easy way to do this with your object implemantation. Your objects are not simple. They contains both data and functions together. So you need to define custom create function for them. But if you can able to separate this like below then you dont have to customize object construction.
For example seperate the MetadataViewModel to two different object :
--> Metadata : which contains only simple data
--> MetadataViewModel : which contains Metadata observableArray and its Metadata manipulator functions
With this structure you can call ko.mapping.fromJSON(newMetaDataArray , {} , MetadataViewModelInstance.MetadataArray) without defining a custom create function at the mapping process.

Resources