I have an issue with patchValue and setValue in Angular 13.
I have a form I created using formBuilder called manageUsers. In it, I have a key called userRoles that contains an array of booleans.
The manageUsers form contains checkboxes for the user roles. The array of booleans turns those checkboxes into checked/unchecked based on the boolean values.
When you first visit the page, the userRoles array in the manageUsers form, by default, look like this...
this.manageUsers.value.userRoles = [false, false, false, false, false]
When a user checks or unchecks a checkbox, the form recognizes a change in state and the "Save Changes" button enables. If someone clicks the "Save Changes" button, then the manageUsers form gets sent to an endpoint where it is saved in a database.
When you select a user, for example "Anthony", the information for that user checks against a list of 5 user roles that looks like this...
[
{
"id": 1,
"name": "AnalyticalAdmin"
},
{
"id": 2,
"name": "Analyst"
},
{
"id": 4,
"name": "AdminReviewer"
},
{
"id": 5,
"name": "SystemAdministrator"
},
{
"id": 6,
"name": "CaseworkSTRTechLeader"
}
]
to see what roles are assigned to Anthony. Let's say "AdminReviewer" is assigned. Then the resulting userRoles array would look like this...
this.manageUsers.value.userRoles = [false, false, true, false, false]
That means the third checkbox ("AdminReviewer") would be appear checked in the form and the others would not be checked. So let's say then you were to check the second checkbox ("Analyst"). The updated userRoles in the manageUsers form would look like this...
this.manageUsers.value.userRoles = [false, true, true, false, false]
What I've done is written code to compare that array with the list of 5 roles above.
The resulting array looks like this...
[
{
"id": 2,
"name": "Analyst"
},
{
"id": 4,
"name": "AdminReviewer"
}
]
So far so good.
Here's my problem...
When I go to patch that array of objects BACK INTO the manageUsers form, the result looks like this...
[
{
"id": 2,
"name": "Analyst"
},
{
"id": 4,
"name": "AdminReviewer"
},
true,
false,
false
]
Angular, for some reason, wants to add booleans to make up for the three missing elements. But here's the thing, I ONLY WANT the this.manageUsers.value.userRoles form object to contain the two objects. I don't need the superfluous booleans.
I just want the this.manageUsers.value.userRoles object (that I'm sending back to the database) to look like this...
[
{
"id": 2,
"name": "Analyst"
},
{
"id": 4,
"name": "AdminReviewer"
}
]
NOT LIKE THIS...
[
{
"id": 2,
"name": "Analyst"
},
{
"id": 4,
"name": "AdminReviewer"
},
true,
false,
false
]
I've tried using BOTH .patchValue and .setValue methods, but they don't work.
How do I patch an array of objects and tell Angular to NOT include booleans for the roles I didn't want to account for?
*UPDATED...
Here is the code I use set the userRoles array...
this.manageUsers = this.formBuilder.group({
id: null,
firstName: ['', Validators.required],
lastName: ['', Validators.required],
userInitial: ['', Validators.required],
netUserName: ['', Validators.required],
workPhone: [null, [Validators.required, Validators.pattern("[0-9 ]{10}")]],
email: ['', Validators.required],
userTitle: ['', Validators.required],
userRoles: new FormArray([], Validators.required),
incidents: new FormArray([], Validators.required)
})
Then, I created a property called userRolesArray...
public get userRolesArray() {
return this.manageUsers.controls.userRoles as FormArray;
}
Then, I created a property called addCheckboxesToManageUsers() and call that in the ngInit() method.
private addCheckboxesToManageUsers(): void {
this.listOfUserRoles.forEach(() => this.userRolesArray.push(new FormControl(false)));
}
after getting value from this.manageUsers.value.userRoles use reducer to remove all boolean type of value.
const data = [
{
"id": 2,
"name": "Analyst"
},
{
"id": 4,
"name": "AdminReviewer"
},
true,
false,
false
]
const newArr = data.reduce((prev, curr) => {
if (typeof curr !== 'boolean') prev.push(curr);
return prev;
}, []);
console.log(newArr)
updated one-liner
data.filter(data => typeof data != 'boolean')
Related
I need to manipulate my API called data to a certain format to show in ant design table (tree data). https://ant.design/components/table/#components-table-demo-tree-data
However, I spent hours and still cannot figure out how I can manipulate the data from API to display in the required format. Below is an example if I am grouping the information based on the projectId.
Each tree will show the projectId, then the information within the tree (the children), will display all the information of the people who is in that project. If a person is in multiple project, then the information will be in all the project that she exist in.
Can anyone please help me on this. How can I manipulate the data to achieve this?
Object gotten from API call that needs to be manipulated:
{
"data": [
{
"email": "alyssayo#xxx.com",
"permissionIds": null,
"roleIds": ["raa","baa","caa"],
"projectIds": ["1aa","3aa"]
},
{
"email": "chiuewww#xxx.com",
"permissionIds": null,
"roleIds": ["baa","caa"],
"projectIds": ["1aa","2aa","3aa"]
},
{
"email": "lalaqq#xxx.com",
"permissionIds": null,
"roleIds": ["caa"],
"projectIds": ["1aa"]
}
],
"statusCode": 200,
"succeeded": true,
"code": "",
"message": "",
"additionalInfo": null
}
After manipulation, the data should look something like this:
const data = [
{
projectIds: "1aa",
children: [
{
email: "alyssayo#xxx.com",
permissionIds: null,
roleIds: ["raa","baa","caa"],
projectIds: "1aa",
},
{
email: "chiuewww#xxx.com",
permissionIds: null,
roleIds: ["baa","caa"],
projectIds: "1aa",
},
{
email: "lalaqq#xxx.com",
permissionIds: null,
roleIds: ["caa"],
projectIds: "1aa",
}
]
},
{
projectIds: "2aa",
children: [
{
email: "chiuewww#xxx.com",
permissionIds: null,
roleIds: ["baa","caa"],
projectIds: "2aa",
}
]
},
{
projectIds: "3aa",
children: [
{
email: "alyssayo#xxx.com",
permissionIds: null,
roleIds: ["raa","baa","caa"],
projectIds: "3aa",
},
{
email: "chiuewww#xxx.com",
permissionIds: null,
roleIds: ["baa","caa"],
projectIds: "3aa",
}
]
}
];
How the table should look like (another example with different attributes and structure):
Code for the example of how the tree table works:
https://codesandbox.io/s/antd-tree-table-forked-qc995o?file=/index.js
It looks like you just want to do a transformation. In this case you might consider the below solution which uses Array#map and Array#reduce to output the format you need.
UPDATED SOLUTION
const projectIdToChildrenDict = apiData.reduce((dict, item) => {
item.projectIds.forEach(projectId => {
if (dict[projectId] === undefined) {
// instantiate empty array if the project id key is not found
dict[projectId] = [];
}
dict[projectId].push(item);
});
return dict;
}, {})
const data = Object.keys(projectIdToChildrenDict).map(key => {
return {
projectIds: key,
children: projectIdToChildrenDict[key]
}
})
It's not a full solution, but it should get you 90% there.
The first section projectIdToChildrenDict is using Array#reduce to build an object. This object is like a dictionary (hence the name) where you can look up a projectId and see all of the elements that contained that project Id.
Then when we are initializing data we are iterating over the previously built dictionary's keys (project ids) using Array#map and returning a new object of the expected output.
I have an object like this:
const objBefore:
{
"id": "3pa99f64-5717-4562-b3fc-2c963f66afa1",
"number": "5000",
"enabled": true,
"classes": [
{
"id": "2fc87f64-5417-4562-b3fc-2c963f66afa4",
"name": "General"
},
{
"id": "7ffcada8-0215-4fb0-bea9-2266836d3b18",
"name": "Special"
},
{
"id": "6ee973f7-c77b-4738-b275-9a7299b9b82b",
"name": "Limited"
}
]
}
Using es6, I want to grab everything in the object except the name key of the inner classes array to pass it to an api.
So:
{
"id": "3pa99f64-5717-4562-b3fc-2c963f66afa1",
"number": "5000",
"enabled": true,
"classes": [
{"id": "2fc87f64-5417-4562-b3fc-2c963f66afa4"},
{"id": "7ffcada8-0215-4fb0-bea9-2266836d3b18"},
{"id": "6ee973f7-c77b-4738-b275-9a7299b9b82b"}
]
}
The closest I got was: let {id, number, enabled, classes: [{id}]} = objBefore;
But it only gets me one id in classes. I've tried spreading above using [...{id}] or [{...id}]. Same thing.
I find it challenging to get the right mental model for how to think about this when it's on multiple levels. In my mind, when I say [...{id}] I'm thinking, "I want the id property as an object in the outer classes array, but give me every id in the array!"
Clearly I'm not thinking about this correctly.
I've tried it using map to get that part but I'm still having trouble combining it back to the original to produce the desired result. for example:
let classIds = objBefore.classes.map(({id}) => {
return {
id
}
})
(Using the map syntax, how can I destructure in the function the other keys that are one level higher?)
To combine them I started trying anything and everything, :
let {id, number, enabled, classIds} = {objBefore, [...classIds]} // returns undefined for all
I'd prefer to do it in one statement. But if that's not possible, then what's a clean way to do it using map?.
You can't destructure and map at the same time in the way you're looking to do it. The main purpose of destructuring assignment is to extract data from an array/object and not for manipulating data. In your case, as you're after an object with the same keys/value as your original object, just with a different classes array, I would instead suggest creating a new object and spreading ... the original object into that. Then you can overwrite the classes array with a mapped version of that array:
const objBefore = { "id": "3pa99f64-5717-4562-b3fc-2c963f66afa1", "number": "5000", "enabled": true, "classes": [ { "id": "2fc87f64-5417-4562-b3fc-2c963f66afa4", "name": "General" }, { "id": "7ffcada8-0215-4fb0-bea9-2266836d3b18", "name": "Special" }, { "id": "6ee973f7-c77b-4738-b275-9a7299b9b82b", "name": "Limited" } ] };
const newObj = {
...objBefore,
classes: objBefore.classes.map(({id}) => ({id}))
};
console.log(newObj);
How about using simple util method with object destructuring, spread operator and map
const objBefore = {
id: "3pa99f64-5717-4562-b3fc-2c963f66afa1",
number: "5000",
enabled: true,
classes: [
{
id: "2fc87f64-5417-4562-b3fc-2c963f66afa4",
name: "General",
},
{
id: "7ffcada8-0215-4fb0-bea9-2266836d3b18",
name: "Special",
},
{
id: "6ee973f7-c77b-4738-b275-9a7299b9b82b",
name: "Limited",
},
],
};
const process = ({ classes, ...rest }) => ({
...rest,
classes: classes.map(({ id }) => ({ id })),
});
console.log(process(objBefore))
In one line, you could do this:
const objAfter = { ...objBefore, classes: objBefore.classes.map(item => ({ id: item.id })) };
Or, if you prefer:
const objAfter = {...objBefore, classes: objBefore.classes.map(({id}) => ({id}))};
There isn't any way in object destructing to copy an entire array of objects into a different array of objects by removing properties so you use .map() for that.
I got JSON data, like:
{
"id": 1,
"active": true,
"dependency": [
{ "id": 2 }
{ "id": 3 }
]
},
{
"id": 2,
"active": true
},
{
"id": 3,
"active": true
}
I want to retrieve the "active" value for each dependency in the id 1 object. So far I used a forEach to get those dependency.
thisElement.dependency.forEach(function(id) {
console.log(id)
}
Which returns id 2 and id 3 as objects. Is there a way to use id.active straight away? By using only one loop? Because the result so far are objects without any connection to the related JSON data. Would it require to loop through the whole JSON data to retrieve those values?
The most efficient thing to to is create a hashmap with an Object or Map first so you only need to iterate the array once to find dependency related values.
You could do it by using Array#find() to search whole array each time but that is far more time complexity than the o(1) object lookup
const activeMap = new Map(data.map(obj => [obj.id, obj.active]))
data[0].dependency.forEach(({id}) => console.log(id ,' active: ' , activeMap.get(id)))
<script>
const data =
[
{
"id": 1,
"active": true,
"dependency": [
{"id": 2 },
{"id": 3}
]
},
{
"id": 2,
"active": false
},
{
"id": 3,
"active": true
}
]
</script>
From my backend I receive message like this:
[
[
{
"id": 1,
"date": "2018-12-31"
},
{
"id": 12,
"standard": null,
"capacity": 7,
"description": null,
}
],
[
{
"id": 26,
"date": "2018-12-08"
},
{
"id": 11,
"standard": null,
"capacity": 7,
"description": null,
}
]
]
I want to map this response to list of 2 objects, so I created class:
export class Response {
id: string;
date: string;
standard: string;
capacity: number;
description: string;
}
I my method, where I calling a service I tried different methods, and even backendData.forEach doesn't work - Angular doesn't recognize backendData as an array.
getData(){
this.service.getData().subscribe(backendData=>{
//how to convert backendData to an array of Response[]?
})
}
I will be very grateful for every help I have been stuck with this for couple of time.
Maybe it helps
getData(){
this.service.getData().subscribe(backendData=> {
//how to convert backendData to an array of Response[]?
return backendData.map(array => Object.assign(array[0], array[1]))
})
}
I'm not sure that you really can convert to Response type this array, because your id is string, but in response you got number, also you got nullable description and standard in response
I'm using mongoose with node.js.
Let's say I have 'Posts' DB where each document in it is a post.
Each post has a 'ReadBy' array which holds names of users that had read this post.
When I'm searching for documents in this DB, I want to "change" the 'ReadBy' value to show by Boolean value if the user that is searching for it is in this array or not.
For example, let's say these are 2 documents that are in this DB:
{ "PostName": "Post Number 1", "ReadBy": ["Tom", "John", "Adam"] }
{ "PostName": "Post Number 2", "ReadBy": ["John", "Adam"] }
If I'm user 'Tom', I want to get the results like this:
[
{
"PostName": "Post Number 1",
"ReadBy": true,
},
{
"PostName": "Post Number 2",
"ReadBy": false,
}
]
Now, I know that I can get the documents and go over each one of them with forEach function, and then use forEach again on the "ReadBy" array and change this field.
I'm asking if there is more efficient way to do it in the mongoDB query itself, or some other way in the code.
If there is another way with mongoose - even better.
Using mongoDb $setIntersection in aggregation you get the result like this :
db.collectionName.aggregate({
"$project": {
"ReadBy": {
"$cond": {
"if": {
"$eq": [{
"$setIntersection": ["$ReadBy", ["Tom"]]
},
["Tom"]
]
},
"then": true,
"else": false
}
},
"PostName": 1
}
})
So above working first like this
{ $setIntersection: [ [ "Tom", "John", "Adam"], [ "Tom"] ] }, return [ "Tom"]
{ $setIntersection: [ [ "John", "Adam"], [ "Tom"] ] }, return [ ]
and $eq to check whether setIntersection results matched with ["Tom"] if yes then return true else false
You can try something similar to
var unwind = {"$unwind": "$ReadBy"}
var eq = {$eq: ["$ReadBy", "Bob"]}
var project = {$project: {PostName: 1, seen: eq}}
db.posts.aggregate([unwind, project])
Just notice that you solution is highly inefficient. Both for storing the data ( growing array) and for searching.