How to map data correctly before subscription? - javascript

I have following function:
this.localStorage.getItem('user').subscribe(user => {
this.user = user;
this.authSrv.getOrders(this.user.einsender).pipe(map(orders => {
map(order => { order["etz"] = "23"; return order})
return orders;
})).subscribe(orders => {
this.orders = orders;
this.completeOrders = orders;
console.log(orders);
this.waitUntilContentLoaded = true;
})
})
The result without the map is:
[{id: 1, etz: "21"}]
With the map from above I try to enter the array, then the order and in the order I try to change the etz property but somehow nothing changes. Can someone look over?
I appreciate any help!

I see multiple issues here.
Try to avoid nested subscriptions. Instead you could use one of the RxJS higher order mapping operators like switchMap. You could find differences b/n different higher order mapping operators here and here.
To adjust each element of the array you need to use Array#map method in addition to the RxJS map operator.
You could use JS spread operator to adjust some of the properties of the object and retain other properties.
Try the following
this.localStorage.getItem('user').pipe(
switchMap(user => {
this.user = user;
return this.authSrv.getOrders(this.user.einsender).pipe(
map(orders => orders.map(order => ({...order, order['etz']: '23'})))
});
})
).subscribe(
orders => {
this.orders = orders;
this.completeOrders = orders;
console.log(orders);
this.waitUntilContentLoaded = true;
},
error => {
// good practice to handle HTTP errors
}
);

map is an operator that goes in a pipe like this:
someObs$.pipe(map(arg => { return 'something'}));
You've done this:
someObs$.pipe(map(arg => {
map(arg => { return 'something' }) // this line here does nothing
return arg;
}));
It doesn't make any sense to use map inside the function you've given to map

Related

How to combine two observables to create new observable?

I have two services named 'PatientsService' and 'AppointmentService'. In third service 'AppointedPatientsService', I want to subscribe to AppointmentService to get all booked appointments with patientId and after that I want to repeatedly subscribe to PatientsService.getPatient(patientId) to get Patient's data with patientId. And then, I want to return new array named allAppointedPatients which holds all appointments with patient's data. I tried this...
getAppointments() {
let allAppointments: Appointment[] = [];
const allAppointedPatients: AppointedPatient[] = [];
return this.appointmentService.fetchAllAppointments().pipe(
take(1),
tap(appointments => {
allAppointments = appointments;
for (const appointment of allAppointments) {
this.patientsService.getPatient(appointment.patientId).pipe(
tap(patient => {
const newAppointment = new AppointedPatient(patient.firstName,
patient.lastName,
patient.address,
patient.casePaperNumber,
appointment.appointmentDateTime);
allAppointedPatients.push(newAppointment);
})
).subscribe();
}
return allAppointedPatients;
}),
pipe(tap((data) => {
return this.allAppointedPatients;
}))
);
}
This is not working and I know there must be better way to handle such scenario. Please help...
You are messing up the async code (observables) with sync code by trying to return the allAppointedPatients array synchronously.
Understand first how async code is working in Javascript and also why Observables (streams) are so useful.
Try the code below and make sure you understand. Of course, I was not able to test it so make your own changes if needed.
getAppointments(): Observable<AppointedPatient[]> {
return this.appointmentService.fetchAllAppointments()
.pipe(
switchMap(appointments => {
const pacientAppointments = [];
for (const appointment of allAppointments) {
// Extract the data aggregation outside or create custom operator
const pacientApp$ = this.patientsService.getPatient(appointment.patientId)
.pipe(
switchMap((pacient) => of(
new AppointedPatient(
patient.firstName,
patient.lastName,
patient.address,
patient.casePaperNumber,
appointment.appointmentDateTime
)
))
)
pacientAppoinments.push(pacientApp$);
}
return forkJoin(pacientAppointments);
});
}
You can use forkJoin:
forkJoin(
getSingleValueObservable(),
getDelayedValueObservable()
// getMultiValueObservable(), forkJoin on works for observables that complete
).pipe(
map(([first, second]) => {
// forkJoin returns an array of values, here we map those values to an object
return { first, second };
})
);

Chaining rxjs operators

I have a function called fetch which is implemented like this:
fetch(): Observable<Option> {
return this.clientNotebookService.getClientNotebook()
.pipe(
map(
clientNotebook => {
this.person = _.find(clientNotebook.persons, i => i.isn === this.isn);
return {
value: this.person.address['TownIsn'],
name: this.person.address['TownName']
};
}
)
);
}
where person is of type RelatedPerson and isn if of type string. The problem with this function is that it doesn't know anything about isn, that's to say, isn should be provided to it in some way.
This value can be extracted in the following way:
this.activatedRoute.params.subscribe(
params => this.isn = params['id']
)
And I would like to do this within the fetch method by combining rxjs operators.
You can chain them like below, just extract the 'id' and pass it to fetch
const isn=this.activatedRoute.params.pipe(pluck('id'))
isn.pipe(mergeMap(isn=>fetch(isn)))
Then add a param to your fetch, then it is available inside the function
fetch(isn){ .....
You can use combineLatest that emits an array of the latest value from both streams every time either emit a value
combineLatest(
this.activatedRoute.params.pipe(map(params => params['id'])),
this.clientNotebookService.getClientNotebook()
).pipe(map(([isn, clientNotebook]) => {
this.person = clientNotebook.persons.find(p => p.isn === isn); // No need for _
return {
value: this.person.address['TownIsn'],
name: this.person.address['TownName']
};
}));

Expected to return a value at the end of arrow function in react

I get the warning in the title when compiling. I understand that it is about not handling some cases of if, but how can I filter before mapping in the correct way?
componentDidMount() {
this.props.UserReducer.user.employeeInfoList.map(role => {
if (role.employeeType) this.rolesOfUser.push(role.employeeType);
if (role.xdockId) this.xdockIdsOfUser.push(role.xdockId);
});
}
It is because you are misusing map which is used for mapping/transforming one array to another. Having a call to map without a return value indicates a problem, as you shouldn't be using it to just iterate over an array performing some action.
It looks like what you really wanted was a forEach call.
To filter an array use Array#filter. Also you can use Array#forEach for your case
componentDidMount() {
this.props.UserReducer.user.employeeInfoList.forEach(role => {
if (role.employeeType) this.rolesOfUser.push(role.employeeType);
if (role.xdockId) this.xdockIdsOfUser.push(role.xdockId);
});
}
Or
componentDidMount() {
const rolesOfUser = this.props.UserReducer.user.employeeInfoList.filter(role => {
return role.employeeType;
})
const xdockIdsOfUser = this.props.UserReducer.user.employeeInfoList.filter(role => {
return role.xdockId;
})
// Do smth with both arrays
}

What is the most comfortable/conventional way to modify a specific item in an array, in a React component state?

I often find my self struggling with manipulating a specific item in an array, in a React component state. For example:
state={
menus:[
{
id:1,
title: 'something',
'subtitle': 'another something',
switchOn: false
},
{
id:2,
title: 'something else',
'subtitle': 'another something else',
switchOn: false
},
]
}
This array is filled with objects, that have various properties. One of those properties is of course a unique ID. This is what i have done recentely to edit a "switchOn" property on an item, according to its ID:
handleSwitchChange = (id) => {
const newMenusArray = this.state.menus.map((menu) => {
if (menu.id === id) {
return {
...menu,
switchOn: !menu.switchOn
};
} else {
return menu;
};
})
this.setState(()=>{
return{
menus: newMenusArray
}
})
}
As you can see, alot of trouble, just to change one value. In AngularJS(1), i would just use the fact that objects are passed by reference, and would directly mutate it, without any ES6 hustle.
Is it possible i'm missing something, and there is a much more straightforward approach for dealing with this? Any example would be greatly appreciated.
A good way is to make yourself a indexed map. Like you might know it from databases, they do not iterate over all entries, but are using indexes. Indexes are just a way of saying ID A points to Object Where ID is A
So what I am doing is, building a indexed map with e.g. a reducer
const map = data.reduce((map, item) => {
map[item.id] = item;
return map;
}, {})
now you can access your item by ID simply by saying
map[myId]
If you want to change it, you can use than object assign, or the ... syntax
return {
// copy all map if you want it to be immutable
...map
// override your object
[id]: {
// copy it first
...map[id],
// override what you want
switchOn: !map[id].switchOn
}
}
As an helper library, I could suggest you use Immutable.js, where you just change the value as it were a reference
I usually use findIndex
handleSwitchChange = (id) => {
var index = this.state.menu.findIndex((item) => {
return item.id === id;
});
if (index === -1) {
return;
}
let newMenu = this.state.menu.slice();
newMenu[index].switchOn = !this.state.menu[index].switchOn;
this.setState({
menu: newMenu
});
}

Firestore - Deep Queries

I have a Firestore that contains a collection (Items) that contains subcollections (called "things"). When I get my Items collection I get all of the child documents but none of the child collections. I would like to do a deep retrieval, one object called Items that contains the sub-collections.
I understand this is not possible out of the box, but I am struggling to write the code myself to do this.
Can anyone help?
constructor(public afs: AngularFirestore) {
//this.items = this.afs.collection('Items').valueChanges();
this.itemsCollection = this.afs.collection('Items', ref => ref.orderBy('year', 'asc'));
this.items = this.itemsCollection.snapshotChanges().map(changes => {
return changes.map(a => {
const data = a.payload.doc.data() as Item;
data.id = a.payload.doc.id;
return data;
});
});
}
getItems() {
return this.items;
}
Yes, you can execute the code below to get what you want to. Basically this code gets for each item its things subcollection:
getItems(): Promise<any> {
return this.db.collection('items').get().then(
items => {
return this.getThingsForItems(items);
});
}
getThingsForItems(items): Promise<any> {
return Promise.all(items.docs.map(async (element) => {
var things = []
const response = await this.db.collection('items')
.doc(element.id).collection('things').get();
response.forEach(subcollectionItem => {
things.push(subcollectionItem.data());
});
return { Item: element.data(), Things: things }
}));
}
The getItems method will return a promisse that contains your items with its things subcollection.
Something like that:
Keep in mind that this query can become a slow-running query since it is making a get for each document of the items collection.
So you might should consider denormalize your database or transform the things subcolletion in an array of the items documents (in this case you should be aware that a document has a max size of 1MB, so do not consider this option if you are array can become too big).

Categories

Resources