tranforming RxJS Observable - javascript

I use angularFirestore to query on firebase and I want join data from multiple documents using the DocumentReference.
The first operator map in the pipe return an array of IOrderModelTable, the second operator, i.e, the switchMap iterate over array an for each element use the id contained in each element to query data in other table.
The problem is that in the swithMap I obtain an array of observable due to anidated map operators. How I can obtain an array of IOrderModelTable and then return an observable of this array.
The code is:
getDataTableOperatorsFromDB(): Observable<IOrderModelTable[]> {
const observable = this.tableOperatorsCollectionsRef.snapshotChanges().pipe(
map(actions => {
return actions.map(a => {
const data = a.payload.doc.data() as IOrdersModelDatabase;
const id = a.payload.doc.id;
data.ot = id;
return data;
});
}),
switchMap(data => {
const result = data.map(element => {
return this.afs.collection('Orders/').doc(element.orderNumberReference.id).valueChanges().pipe(map(order => {
return {
otNumber: element.ot,
clientName: '',
clientReference: order.clientReference,
id: element.orderNumberReference,
};
}));
});
// Result must be an IOrderModelTable[] but is a Observable<IOrderModelTable>[]
return of(result);
})
);

You can use to Array operator to transform a stream to an array, but make sure your stream will end.
The trick is to choose the right stream.
For you problem, the natural source would be the list received by your first call. In a schematic way I can put it , you get a list of ids, that you transform into a list of augmented information :
first input ...snapshopChanges():
----[A, B, C]------>
each element is transformed through ...valueChanges():
-------Call A -------------DataA-------->
-------Call B ------------------------DataB----->
-------Call C --------------------DataC----->
Then reduced using toArray() to :
----------------------------------------------[DataA, DataC, DataB]-------->
Code:
getDataTableOperatorsFromDB(): Observable<IOrderModelTable[]> { {
return this.tableOperatorsCollectionsRef.snapshotChanges()
.pipe(
map(actions => {
from(data).pipe(
map(action => {
const data = a.payload.doc.data() as IOrdersModelDatabase;
const id = a.payload.doc.id;
data.ot = id;
return data;
}),
mergeMap(element => {
return this.afs.collection('Orders/').doc(element.orderNumberReference.id).valueChanges().pipe(
map(order => {
return {
otNumber: element.ot,
clientName: '',
clientReference: order.clientReference,
id: element.orderNumberReference,
};
})
);
}),
toArray()
);
})
)
}
Important : I replaced switchMap by mergeMap, otherwise some information could be thrown away.

#madjaoue
You're right, mergeMap is the correct operator in this case because with switchMap for each event emitted the inner observable is destroyed so in the subscribe you only get the final event emitted, i.e, the last row. This observable is long lived, never complete, so also use the operator take with the length of the actions which is the array that contains the list of documents.
Thank you very much for the help. :D
getDataTableOperatorsFromDB(): Observable<IOrderModelTable[]> {
const observable = this.tableOperatorsCollectionsRef.snapshotChanges().pipe(
switchMap(actions => {
return from(actions).pipe(
mergeMap(action => {
console.log(action);
const data = action.payload.doc.data() as IOrdersModelDatabase;
const otNumber = action.payload.doc.id;
return this.afs.collection('Orders/').doc(data.orderNumberReference.id).valueChanges().pipe(map(order => {
return {
otNumber: otNumber,
clientName: '',
clientReference: order.clientReference,
id: data.orderNumberReference,
};
}));
}),
mergeMap(order => {
console.log(order);
return this.afs.collection('Clients/').doc(order.clientReference.id).valueChanges().pipe(map(client => {
return {
otNumber: order.otNumber,
clientName: client.name,
clientReference: order.clientReference,
id: order.id,
};
}));
}),
take(actions.length),
toArray(),
tap(console.log),
);
}),

Related

Smarter way of using filter and map instead of filter and loop

I want to create a smarter way of coding of the following example. Important is that each loop (for activeFilters) needs to be fully done, before we want to return the filtersTest.
const createFilters = async () => {
const filtersTest = [] as any
// Only create active filters by checking count.
const activeFilters = getComponentFilter.value.filter(function(item) {
if (item.items) {
return item.items.some((obj) => obj.count)
}
});
// Loop through the active filters and push arrays into the object.
for(let i = 0 ; i < activeFilters.length; i++) {
const options = await createFilterOptions(activeFilters[i].id, activeFilters[i].items);
const array = {
defaultValue: null,
id: activeFilters[i].id,
value: 'nee',
label: activeFilters[i].label,
options: options,
}
filtersTest.push(array)
}
return filtersTest;
}
First of all, it should be clear that createFilters is not going to return the array, but a promise that will eventually resolve to that array.
With that in mind, you can reduce your code a bit, using Promise.all, the ?. operator, destructuring parameters, and shorthand property names in object literals:
const createFilters = () => Promise.all(
getComponentFilter.value.filter(({items}) =>
items?.some((obj) => obj.count)
).map(({id, label, items}) =>
createFilterOptions(id, items).then(options => ({
defaultValue: null,
id,
value: 'nee',
label,
options
}))
)
);

How to change update an object within a useState array

Suppose we have an array of objects in userInformation:
[
{
firstName:'Bob',
lastName:'Dude',
},
{
firstName:'John',
lastName:'Rad',
}
]
const [userInformation, setUserInformation] = useState([]);
userInformation.forEach((user, index) => {
if (snapshot.val().leadID === user.leadID) {
setUserInformation((userInformation) => ({
...userInformation,
[index]: snapshot.val(),
}));
}
});
I would like to update the second object.
My code doesn't seem to be working quite right. Any suggestions?
Yes few suggestions I have for you:)
First of all you have never assigned userinformation to your state.
So it should be some what like below
const [userInformation, setUserInformation] = useState(userinformation);
Or you must be getting userinformation from an API and using useEffect to initialize it.
Second thing is you must have an id as well as index key on each user something like this:
[
{
leadId:1,
firstName:'Bob',
lastName:'Dude',
index:1
},
{
leadId:2,
firstName:'John',
lastName:'Rad',
index:2
}
]
Now, Coming to what you are expecting you can use map() function in such as way that once the condition is met, you update that particular user, otherwise you should return back same users when condition is not met.
const updatedUsers = userInformation.map((user, index) => {
if (snapshot.val().leadID === user.leadID) {
return setUserInformation((userInformation) => ({
...userInformation,
[index]: snapshot.val(),
}));
}
return userInformation;
});
Here I think a simple find() would do the trick, rather than trying to loop the array.
const updateInfo = (id, obj /* snapshot.val() */) => {
const item = userInformation.find(({ leadID }) => leadID === id);
const updatedItem = {
...item,
...obj
};
setUserInformation((previousInfo) => {
...userInformation,
...updatedItem
})
}
Sorry for the lack of information provided in my question.
I was using firebase realtime with React, and not wrapping my logic in a useEffect was one problem.
I went with this solution:
setUserInformation(
userInformation.map((user) =>
user.leadID === snapshot.val().leadID
? {
...user,
...snapshot.val(),
}
: user
)
);
Thank you for all your answers!

Optimize repeated expensive computations in Rxjs given an Observable of Array

Given the following script:
type Data = {
id: string
value: number
}
// This is a pure function: given val, res will always be the same
const veryExpensiveCalc = (val: number) => {
const res = // ... 5 seconds of sync computation ...
return res
}
const array$ = new ReplaySubject<Array<Data>>(1)
const allComputedValues$ = array$.pipe(
map(arr =>
arr.map(item =>
// I want this veryExpensiveCalc to be performed only when
// item.value changes for this item.id, or the item was not
// there before
veryExpensiveCalc(item.value)
)
)
)
allComputedValues$
.pipe(
tap(newStruct => {
console.log('newStruct', newStruct)
})
)
.subscribe()
I'd like to optimize how allComputedValues$ is calculated. In particular let's say that an item is added or removed to array$, or the order of the elements change: then veryExpensiveCalc is executed again on every item of the array, even though that's not needed at all. I just need allComputedValues$ from the previous computation with the result of veryExpensiveCalc applied only to the newly added element (in case of addition).
What's the best way to write this in a functional reactive fashion? It should work also if there are multiple elements edited/added/removed at the same time (so that veryExpensiveCalc is executed only on the elements that have a different value given the same id).
I think you can use the scan() operator here:
const allComputedValues$ = array$.pipe(
scan(
(acc, crtArr) => {
// with these we 'automatically' remove the current items(from `acc`) whose ids are not in the current array
const newIds = {};
const newValues = {};
crtArr.forEach(crt => {
const { id: currentId, value: crtValue } = crt;
// if new or edited
if (!acc.ids[currentId] || acc.values[currentId] !== crtValue) {
newIds[currentId] = true;
newValues[currentId] = veryExpensiveCalc(crtValue);
}
});
return { ids: newIds, values: newValues };
},
{ ids: {}, values: {} },
),
)

Edit function not saving changes to state data in React

I am trying to provide functionality in my webpage for editing state data.
Here is the state structure
state = {
eventList:[
{
name: "Coachella"
list: [
{
id: 1,
name: "Eminem"
type: "rap"
}
{
id: 2,
name: "Kendrick Lamar"
type: "rap"
}
]
}
]
}
I want to be able to edit the list arrays specifically the id, name, and type properties but my function doesn't seem to edit them? I currently pass data I want to override id name and type with in variable eventData and an id value specifying which row is selected in the table which outputs the state data.
Here is the function code:
editPickEvent = (eventData, id) => {
const eventListNew = this.state.eventList;
eventListNew.map((event) => {
event.list.map((single) => {
if (single.id == id) {
single = eventData;
}
});
});
this.setState({
eventList: eventListNew,
});
};
When I run the code the function doesn't alter the single map variable and I can't seem to pinpoint the reason why. Any help would be great
edit:
Implementing Captain Mhmdrz_A's solution
editPickEvent = (eventData, id) => {
const eventListNew = this.state.eventList.map((event) => {
event.list.map((single) => {
if (single.id == id) {
single = eventData;
}
});
});
this.setState({
eventList: eventListNew,
});
};
I get a new error saying Cannot read property list of undefined in another file that uses the map function to render the state data to the table?
This is the part of the other file causing the error:
render() {
const EventsList = this.props.eventList.map((event) => {
return event.list.map((single) => {
return (
map() return a new array every time, but you are not assigning it to anything;
editPickEvent = (eventData, id) => {
const eventListNew = this.state.eventList.map((event) => {
event.list.forEach((single) => {
if (single.id == id) {
single = eventData;
}
});
return event
});
this.setState({
eventList: eventListNew,
});
};
const editPickEvent = (eventData, id) => {
const updatedEventList = this.state.eventList.map(event => {
const updatedList = event.list.map(single => {
if (single.id === id) {
return eventData;
}
return single;
});
return {...event, list: updatedList};
});
this.setState({
eventList: updatedEventList,
});
}
Example Link: https://codesandbox.io/s/crazy-lake-2q6ez
Note: You may need to add more checks in between for handling cases when values could be null or undefined.
Also, it would be good if you can add something similar to the original data source or an example link.
Turns out primitive values are pass by value in javascript, which I didn't know and why the assignment wasn't working in some of the previous suggested answers. Here is the code that got it working for me:
editEvent = (EventData, id) => {
const eventListNew = this.state.eventList.map((event) => {
const newList = event.list.map((single) => {
return single.id == id ? EventData : single;
});
return { ...event, list: newList };
});
this.setState({
eventList: eventListNew,
});
};

I'm looking for a way to remove items from my select array based on values of other array

I'm trying to remove from select array the options who match with my other array, both come from api and are beeing displayed through ngFor, i'm already trying to do it using filter but with no success, code below as follow:
loadUnidadeUsuarios() {
return this.restApi.getUnidadeUsuarios(this.id).subscribe((data: UnidadeUsuario) => {
this.unidadeUsuarioData = data;
});
}
Then after load UnidadeUsuarioData i need to remove the users who match:
loadUsuarios() {
return this.restApi.getUsuarios().subscribe((data: Usuario) => {
this.Usuario = data;
this.Usuario = this.Usuario.filter(usuario => usuario.id !== this.unidadeUsuarioData.id )
});
}
But with no success
the filter it's looks like
this.Usuario.filter(usuario=>!this.unidadeUsuario.find(x=>x.id==usuario.id)
But you must take account if you need wait to the two calls finish. For this use switchMap
this.restApi.getUnidadeUsuarios(this.id).pipe(
switchMap((res: any[]) => {
//..here you has your getUnidadeUsuario...
//but you want the others usuarios
return this.restApi.getUsuarios().pipe(map((usuarios: any[]) => {
//in usuarios you has all the usuarios
//but you want filtered
return usuarios.filter(u => !res.find(x => x.id == u.id))
}))
})
).subscribe(usuarios => {
console.log(usuarios)
})
To achieve expected result, with just two loops, use below option
Convert unidadeUsuarioData as obj with id values
Filter Usuario to check whether unidadeUsuarioData has property id
loadUsuarios() {
let unidadeUsuarioDataObj = this.unidadeUsuarioData.reduce((acc, v) => {
acc[v.id] = v;
return acc
}, {})
this.Usuario = this.Usuario.filter(usuario => !unidadeUsuarioDataObj[usuario.id])
console.log(this.Usuario)
}
stackblitz for reference- https://stackblitz.com/edit/angular-dapycb?file=src/app/app.component.html

Categories

Resources