How to load additional data for a collection with rxjs - javascript

I'm struggeling to understand the different rxjs operators. For example when i've got a collection of objects and i want to add additional data from an api to each object.
e.g.
people = [{id: 1, name: null}, {id:2, name: null}]
from(people).pipe(
map(person => {
return api.getName(person.id).pipe(
map(name => person.name = name)
)
})
).subscribe(people =>
console.log(people) // should be [{id: 1, name: bob}, {id:2, name: alice}]
)
I tried using mergeMap, map, switchMap in different variations but i never figured out how to get map the additional data into the array.

Your issue is likely when you are using map/switch map you are returning the result of the assignment person.name = name;
When you use an arrow function without curly braces like this
() => something;
It is actually shorthand for
() => {
return something
}
Here is a working example, with some sloppy class creation to make it work...
https://stackblitz.com/edit/angular-ivy-xfunnc?file=src%2Fapp%2Fapp.component.ts
people = [{id: 1, name: null}, {id:2, name: null}];
peopleWithNames = from(this.people).pipe(
switchMap(person => {
return this.api.getName(person.id).pipe(
map(name => {
// set the person's name property
person.name = name;
// but return the whole person object, not just the retrun from the assignment
return person;
})
)
})
).subscribe(p => {
console.log(p)
})

If you want a list, I think do you need use forkJoin. You map each element of people to a call
people = [{id: 1, name: null}, {id:2, name: null}];
forkJoin(people.map(p=>this.api.getName(person.id))
.subscribe((res:any[])=>{
res.forEach((p:any,index)=>{
people[index]={...people,..p}
})
})
Well you can use map too to get the response with the full elements
people = [{id: 1, name: null}, {id:2, name: null}];
forkJoin(this.people.map(p=>this.api.getName(p.id))).pipe(
map((res:any[])=>{
const result=this.people.map((p,index)=>
//if the response is an object map to
//({...p,...res[index]})
//if the response is a string,
({...p,name:res[index]})
)
return result
})
).subscribe(res=>{console.log(res)})

Related

Why doesn't reassigning the parameter element in forEach work

For the following code block:
const items = [
{ id: 1, name: 'one' },
{ id: 2, name: 'two' },
];
const changes = {
name: 'hello'
}
items.forEach((item, i) => {
item = {
...item,
...changes
}
})
console.log(items) // items NOT reassigned with changes
items.forEach((item, i) => {
items[i] = {
...item,
...changes
}
});
console.log(items) // items reassigned with changes
Why does reassigning the values right on the element iteration not change the objects in the array?
item = {
...item,
...changes
}
but changing it by accessing it with the index does change the objects in the array?
items2[i] = {
...item,
...changes
}
And what is the best way to update objects in an array? Is items2[i] ideal?
Say no to param reassign!
This is a sort of a fundamental understanding of higher level languages like JavaScript.
Function parameters are temporary containers of a given value.
Hence any "reassigning" will not change the original value.
For example look at the example below.
let importantObject = {
hello: "world"
}
// We are just reassigning the function parameter
function tryUpdateObjectByParamReassign(parameter) {
parameter = {
...parameter,
updated: "object"
}
}
tryUpdateObjectByParamReassign(importantObject)
console.log("When tryUpdateObjectByParamReassign the object is not updated");
console.log(importantObject);
As you can see when you re-assign a parameter the original value will not be touched. There is even a nice Lint rule since this is a heavily bug prone area.
Mutation will work here, but ....
However if you "mutate" the variable this will work.
let importantObject = {
hello: "world"
}
// When we mutate the returned object since we are mutating the object the updates will be shown
function tryUpdateObjectByObjectMutation(parameter) {
parameter["updated"] = "object"
}
tryUpdateObjectByObjectMutation(importantObject)
console.log("When tryUpdateObjectByObjectMutation the object is updated");
console.log(importantObject);
So coming back to your code snippet. In a foreach loop what happens is a "function call" per each array item where the array item is passed in as a parameter. So similar to above what will work here is as mutation.
const items = [
{ id: 1, name: 'one' },
{ id: 2, name: 'two' },
];
const changes = {
name: 'hello'
}
items.forEach((item, i) => {
// Object assign just copies an object into another object
Object.assign(item, changes);
})
console.log(items)
But, it's better to avoid mutation!
It's better not mutate since this can lead to even more bugs. A better approach would be to use map and get a brand new collection of objects.
const items = [{
id: 1,
name: 'one'
},
{
id: 2,
name: 'two'
},
];
const changes = {
name: 'hello'
}
const updatedItems = items.map((item, i) => {
return {
...item,
...changes
}
})
console.log({
items
})
console.log({
updatedItems
})
As the MDN page for forEach says:
forEach() executes the callbackFn function once for each array
element; unlike map() or reduce() it always returns the value
undefined and is not chainable. The typical use case is to execute
side effects at the end of a chain.
Have a look here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach
This means that although you did create new object for item, it was not returned as a value for that index of array. Unlike your second example, the first one is not changing original array, but just creates new objects and returns undefined. This is why your array is not modified.
I'd go with a classic Object.assign for this:
const items = [
{ id: 1, name: 'one' },
{ id: 2, name: 'two' },
];
const changes = {
name: 'hello'
}
items.forEach( (item) => Object.assign(item,changes) )
console.log(items)
Properties in the target object are overwritten by properties in the sources if they have the same key. Later sources' properties overwrite earlier ones.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
The other approach you can take is to use map and create a new array based on the original data and the changes:
const items = [
{ id: 1, name: 'one' },
{ id: 2, name: 'two' },
];
const changes = {
name: 'hello'
}
const newItems = items.map((item) => {
...item,
...changes
})
console.log(newItems);
But if you need to modify the original array, it's either accessing the elements by index, or Object.assign. Attempting to assign the value directly using the = operator doesn't work because the item argument is passed to the callback by value not by reference - you're not updating the object the array is pointing at.

Checking an object that contains arrays of objects for a value

I see this question has been touched on a lot on stack overflow but I can't seem to find one that helps my issue. I have an object that contains many arrays with objects nested inside. I need to find the key of the array that contains the object that a usersID. Ive tried .filter and for in loops but I keep getting errors.
My data looks like this :
{
FL: [{id: "mp-2d24973e-c610-4b1c-9152...}{id...}],
TX: [{id: "mp-2d24973e-c610-4b1c-9152...}{id...}],
LA: [{id: "mp-2d24973e-c610-4b1c-9152...}{id...}],
}
Is there a method that allows me to check for a value inside the arrays and if it is found returns the key to that array ie. FL or TX?
const practices = data.items
for (const [key, value] of Object.entries(practices)){
console.log(key, value, "KEYVALUE")
if(value.id === currentUser.currentPracticeID){
console.log(key)
}
}
Of course this code doesnt work but this what Ive tried so far. Im still new to dev so any point in the right direction would be great.
A simple filter() inside of a for...in loop would do it. After looking at the other answers, I should say this will return the first matching ID, rather than an array of all matching ID's like #Barmars answer
const practices = {
FL: [{id: "mp-2d24973e-c610-4b1c-9152"},{id:"3"}],
TX: [{id: "mp-2d24973e-c610-4b1c-9153"},{id:"4"}],
LA: [{id: "mp-2d24973e-c610-4b1c-9154"},{id:"5"}],
}
const findUserKey = (u) => {
for (const key in practices) {
if (practices[key].filter(mp => mp.id === u.currentPracticeID).length>0) return key
}
return false;
}
let currentUser = {currentPracticeID: "mp-2d24973e-c610-4b1c-9154"}
let check_mp = findUserKey(currentUser)
console.log(check_mp)
You can use Object#keys to get the list of object keys, Array#find to iterate over this list, and Array#findIndex to check if the array at each iteration has the userId you're searching for:
const getKeyOfUserId = (obj = {}, userId) =>
Object.keys(obj).find(key =>
obj[key].findIndex(({ id }) => id === userId) >= 0
);
const obj = {
FL: [{id: "mp-2d24973e-c610-4b1c-9152"},{id:"mp-2d24973e-c610-4b1c-9153"}],
TX: [{id: "mp-2d24973e-c610-4b1c-9154"},{id:"mp-2d24973e-c610-4b1c-9155"}],
LA: [{id: "mp-2d24973e-c610-4b1c-9156"},{id:"mp-2d24973e-c610-4b1c-9157"}]
};
console.log( getKeyOfUserId(obj, "mp-2d24973e-c610-4b1c-9156") );
filter() is an array method, practices is an object. You need to use Object.entries() to get an array of keys and values.
Then you can use .some() to test if any of the objects in the nested array contain the ID you're looking for.
const practices = {
FL: [{
id: "mp-2d24973e-c610-4b1c-9152"
}, {
id: "mp-2d24973e-c610-4b1c-9153"
}],
TX: [{
id: "mp-2d24973e-c610-4b1c-9154"
}, {
id: "mp-2d24973e-c610-4b1c-9155"
}],
LA: [{
id: "mp-2d24973e-c610-4b1c-9156"
}, {
id: "mp-2d24973e-c610-4b1c-9157"
}]
};
let currentPracticeID = "mp-2d24973e-c610-4b1c-9156";
const states = Object.entries(practices).filter(([key, arr]) => arr.some(({
id
}) => id == currentPracticeID)).map(([key, arr]) => key);
console.log(states);
Here is how I would do it:
const data = {
FL: [{id: 'ab'}, {id: 'cd'}, {id: 'ef'}],
TX: [{id: 'hi'}, {id: 'jk'}, {id: 'lm'}],
LA: [{id: 'no'}, {id: 'pq'}, {id: 'rs'}]
};
const findKey = (id) => {
let foundKey;
Object.entries(data).some(([key, objects]) => {
if (objects.find(object => object.id === id)) {
foundKey = key;
return true;
}
});
return foundKey;
};
console.log(`The data: ${JSON.stringify(data)}`);
console.log(`Looking for object with ID "jk": ${findKey('jk')}`);
You loop through the entries of your data, so you have the key and the objects for that key. You simply use objects.find to see which object matches the ID you're looking for. I would use array.some for this as it stops the loop when you return true, and foundKey will simply be falsy if nothing is found.

Redux managing arrays of objects and finding nested objects

I'm not sure what to name this, but basically I'm new to React and Redux and looking for a more correct/cleaner way to do this or just how to do this with my current set up.
I have a state that looks like this
--Character
---id
---name
---race
----id
----raceName
----traits
-----trait
------id
------name
------description
-----trait
------id
------name
------description
---classes
----class
-----id
-----className
-----classLevel
-----traits
------trait
-------id
-------name
-------description
------trait
-------id
-------name
-------description
----class
-----id
-----className
-----classLevel
-----traits
------trait
-------id
-------name
-------description
------trait
-------id
-------name
-------description
---traits
----trait
-----id
-----name
-----description
----trait
-----id
-----name
-----description
As you can see(hopefully) traits is an array of object TRAIT and classes is an array of object CLASS, in the end the whole state is quite a messy deal. I've read that I can somehow reference them by ID's but I'm not sure how if IDs are autogenerated.
So I kind of have two questions:
How do I simplify/flatten this structure if it even could be done?
If I can't simplify this structure is there anyway I can find a specific Trait with a specific ID without looping through all the objects that have property traits?
Yes. You can find Trait with a specific ID easily. Let know if this is what you are asking.
// Search in traits directly under Character.
const traitForId = this.state.Character.traits.find((trait) => {
return trait.id = "<SPECIFIC_ID>"
})
// Search in the list of traits under each Class.
const classTraits = this.state.Character.classes.map((class) => class.traits).flat();
const classTraitsForId = classTraits.find((trait) => {
return trait.id = "<SPECIFIC_ID>"
})
Find below recursive way to find a Trait irrespective of where it's present in the state.
function findTraitForId(state, specific_id){
if(state.traits){
const traitForId = state.traits.find((trait) => {
return trait.id == specific_id
});
if(traitForId)
return traitForId;
}
return Object.keys(state).filter((key) => key != 'traits').map((stateItem) => {
return findTraitForId(state[stateItem], specific_id);
}).flat();
}
Tried above function for the input
findTraitForId({'classes':[{traits: [{id: 1, name: "A"}, {id: 2, name: "AB"}]}, {traits: [{id: 3, name: "ABC"}, {id: 4, name: "ABCD"}]}], traits: [{id: 5, name: "ABCDE"}, {id: 6, name: "ABCDEF"}]}, 3)
which return
[{id: 3, name: "ABC"}]

_.map doesn't return an array of objects, but strings

I have an array of objects:
[{name:'john',age: 24}, {name:'arian', age: 34}]
I want to get the following array back:
[{title:'john'},{title:'arian'}]
The following code:
let tableData = {
header: [{title: 'name'}],
data: _.map(groups, group => {
return group.name
})
};
results in: ['john', 'arian']
but this code:
let tableData = {
header: [{title: 'name'}],
data: _.map(groups, group => {
return {title: group.name} // <-- this line changed
})
};
returns an array of length 0 for data. Why does this happen ? isn't it basically the same ?
Update 1
How I get groups:
const { groups } = this.props;
This is a react code.
The problem stated has a very simple solution. You are doing it right, but need little change in the code
var obj = [{name:'john',age: 24}, {name:'arian', age: 34}];
var result = _.map(obj,(value)=>{
return {name:value.name};
});
If you are expecting each member of your resultant array to be a object then you need to return that kind of object from your map, as simple as that. Either you use lodash or pure javascript version of map, answer is the same:
_.map(groups, o=>({'title': o.name}))
Or with native javascript Array map
groups.map(o=>({'title': o.name}))
Why not use native Array.prototype.map?
let data = [{name:'john',age: 24}, {name:'arian', age: 34}]
console.log(data.map( (item) => { return { title: item.name } }))

Why does my spread operator show this behaviour?

let schools = [
{ name: "Yorktown"},
{ name: "Stratford" },
{ name: "Washington & Lee"},
{ name: "Wakefield"}
]
let updatedSchools = editName("Stratford", "HB Woodlawn", schools)
console.log( updatedSchools[1] ) // { name: "HB Woodlawn" }
const editName = (oldName, name, arr) =>
arr.map(item => {
if (item.name === oldName) {
// what is happening below!?
return {
...item,
name
}
} else {
return item
}
})
first of all, i'm sorry if this question might be easy for you, but i'm having trouble understanding how the return statement of the snippet works and would really appreciate help.
return { ...item, name }
So i would expect updatedSchool to be (even though it's invalid syntax):
[
{name: "Yorktown"},
{ name: "Yorktown", "HB Woodlawn"},
{ name: "Washington & Lee"},
{ name: "Wakefield"}
]
why does it produce { name: "HB Woodlawn" }?
Simply desugar expression step by step
{...item, name }
First {name} is shortcut for {name: name}
Then {...obj} is the same as Object.assign({}, obj)
Combining both gives Object.assign({}, obj, {name: name})
Given obj = {name: 'Stratford'} has only one property name it will simply create new object and replace name with a new one.
You can read about Object.assign here
return { // the spread operator assigns existing properties of item
...item, // to the new returned object
name // similar to return Object.assign(item, {name: name})
}
The rest parameter can work on objects as well as arrays in browsers that support it. If you want to understand the code, it's best to walk through it.
editSchools is a function that takes an oldName, a name, and an array. It returns the result of the mapping from array to a new array. Each element in the new array is determined by the callback function that map executes. If the item's name property is equal to the oldName, then a new object is created which will take its place, {...item, name}. This is where the confusion lies.
It does something weird. The new object recieves all the keys of the item object, and then it will define (or redefine) the name property to the value of name provided to editSchools.
So in essence, this code finds the objects that have a name key whose value is oldName and replaces it with an identical new object with a changed name property to the new name value.

Categories