I would like to setup just one subscription from these two subscriptions
this.dfs.mainProductQuery('data').pipe(
map((items) => {
this.arrayItems = items;
})).subscribe();
this.cj.getData().pipe(
map((val, i) => {
return val;
})).subscribe();
The mainProduct() return 25 records that contains an ID.
The getData() returns 1,000 records. I want to get only one subscription with data from both and match them up by id. The ids in each are the same.
I attempted to add
this.products = combineLatest(this.affProducts, this.dogfoods);
But the this.affProducts is erroring saying {} is not assignable to Subscribe
I am not sure if this is what you are looking for but have you tried the assign function. So something like this:
{let combined = Object.assign(obj1, obj2);}
what that would do is combine the two objects but any matches, it would take the first instance of any key that happens to be there.
Related
I am working with a VueX store at the moment, and I have 1 mutation that does a push to entire array. Everything works currently, but want to know if I can make this code simpler, less lines / more optimized at the same time.
Side note, these are objects stored in an array.
PUSH_TO_ALL: (state, data) => {
const found = state.all.find((one, i) => {
if (one.slug === data.slug) {
state.all[i] = data // modify current state
return true
}
return false
})
if (!found) {
state.all.push(data) // add to the state
}
}
My first thought is that if you compare the slug in data to that in every object in the array, it must be unique (otherwise you would replace multiple objects in the find).
This means that you can almost certainly make things a lot faster and a lot simpler if you switch from having the 'root' of state be an array, to using an object instead, indexed by slug.
Then your code would switch to being something like:
PUSH_TO_ALL: (state, data) => {
state.all[data.slug] = data
This has 2 advantages - it is much simpler and faster to modify state, since you don't need to walk all of state checking if the object already exists. And secondly there's no need for separate code to distinguish between adding a new object, and replacing it if it already exists.
If for some reason you have to store state as an array, I would use a different part of state to maintain an object which tracks slug to array index. Then your code would be something like:
PUSH_TO_ALL: (state, data) => {
if (state.map[data.slug]) {
state.all[state.map[data.slug]] = data
} else {
// Push returns length of array - index is len-1
state.map[data.slug] = state.all.push(data) - 1
}
Note - in Vue2 you may need to use Vue.set() to update nested objects, since otherwise the code may not react to these changes. Vue3 no longer has this limitation.
You could use Array.findIndex instead of Array.find, and pair it with a ternary to make the trivial logic more concise (though not necessarily clearer).
const mutations = {
PUSH_TO_ALL: (state, data) => {
const indexOfMatchingSlug = state.all.findIndex(one => one.slug === data.slug);
state.all[indexOfMatchingSlug] = data ? indexOfMatchingSlug > -1 : state.all.push(data);
}
}
Array.findIndex documentation
JavaScript ternary operator documentation
Currently, I am working on a pretty complicated set of queries to firestore.
I am trying, all at once, to populate an array full of references to other documents, and then read those documents and put the information in an array.
More specifically to this example, I have 4 references in one collection. I want to get those references, and then using those references, query 4 documents and populate an array with the information.
The order is as follows: do a query for all of the documents in the tags subcollection, which is handled by the function below:
getTagsOnPage(workspaceId: string, pageId: string) {
// get all of the references in the tags sub-collection, and puts them in an array
// get all id's, do not get data
return this.afs
.collection("users")
.doc(`${auth().currentUser.uid}`)
.collection<Workspace>("workspaces")
.doc(`${workspaceId}`)
.collection<Page>("pages")
.doc(`${pageId}`)
.collection("tags")
.snapshotChanges()
.pipe(
map((actions) => {
return actions.map((a) => {
const ref = a.payload.doc.get("reference");
return ref; // return an array of (id: reference) key-value pairs
});
})
);
}
This works fine with the following code performing the subscription:
this.pageService
.getTagsOnPage(this.workspaceId, this.currentPage.id)
.subscribe((data) => {
temp = data;
});
data is as follows, via the console:
(3) ["/users/ucB5cF4Pj3PWhRn10c9mvqQbS7x2/workspaces/It1…1tSnPI5GJrniY82vZL/localTags/1p5Tscwn14PyK6zRaFHX", "/users/ucB5cF4Pj3PWhRn10c9mvqQbS7x2/workspaces/It1tSnPI5GJrniY82vZL/localTags/lFKoB0jngmtnALut2QS2", "/users/ucB5cF4Pj3PWhRn10c9mvqQbS7x2/workspaces/It1tSnPI5GJrniY82vZL/localTags/r6sf2SX6v87arU2rKsD5"]
Now, to perform the next set of data reads is where my confusion begins.
My initial approach was to try a for loop (for the length of this array), but this would involve iterating performing a number of nested subscriptions, which I do not think is possible in this sense.
I am fairly new to rxjs, and have only used the map and switchMap operators. In this case, I am imagining I would use something like mergeMap and/or flatMap, but frankly, I am not sure how to make this work in this case. Also, dealing with the for loop where I need to grab documents based on the array of documentReferences I get is also throwing me for a loop.
This is my current implementation, which is all over the place; I hope the feel for what I am trying to do is there. Basically, get the array of references via getTagsOnPage, wait for the observable to end, use switchMap to take the data array and loop over it; ideally, subscribe to each ref and add to tagData, and then return that:
let tagData;
this.pageService.getTagsOnPage(this.workspaceId, this.currentPage.id).pipe(
switchMap((data) => {
let references = data;
for (let j = 0; j < references.length; j++) {
let ref = this.afs.doc(`${references[j]}`);
ref.snapshotChanges().pipe(
map((actions) => {
const data = actions.payload.data();
tagData.push(data);
})
);
// push the data (different data var)
}
})
);
return tagData;
Messy, I know, but I think once I know the right operators to use this will make a lot more sense.
Also, atm this returns an empty array. There is an error for when I use switchMap that says the following:
Argument of type '(data: any[]) => void' is not assignable to parameter of type '(value: any[], index: number) => ObservableInput<any>'.
Type 'void' is not assignable to type 'ObservableInput<any>'.
Thank you for any help!
The reason for your error using switchMap is because you are not returning an Observable.
When using any of the "Higher Order Mapping Operators" (switchMap, concatMap, mergeMap, etc), the provided function must return an observable. As the error states: "Type void is not assignable to type ObservableInput<any>", you aren't returning anything (void).
The next thing that isn't quite right is that within your loop, you reference ref.snapshotChanges().pipe(), but you never subscribe to it. As you may know, observables are lazy and won't fire unless there is a subscriber.
As long as you return an observable inside of switchMap(), it will automatically subscribe to it and emit its values.
Let's think about this a little bit differently; instead of looping over the results of your first call, executing them, then pushing the values into an array. We can instead take the results and turn them into an observable stream that emits all the results of their individual calls, and combines them into an array for you. But... with a subtle difference: I'm suggesting not to have a separate tagData array outside the stream, but to have your observable return the tagData form that you need as an Observable<TagData[]>.
I think something like this will work for you:
tagData$ = this.pageService.getTagsOnPage(this.workspaceId, this.currentPage.id).pipe(
switchMap(references => from(references)),
mergeMap(ref => this.afs.doc(ref).snapshotChanges()),
map(actions => actions.payload.data()),
scan((allTagData, tagData) => [...allTagData, tagData], [])
})
);
Let's break that down!
We use from to create an observable from your array, that emits each "reference" one at a time.
mergeMap will subscribe to the observable we are creating and emit its emissions
map simply transforms the value into your desired shape
scan accumulates your emissions for you and emits on each change. if you don't want to emit until all calls come back, use reduce() instead
Now, you can simply do: tagData$.subscribe() and do what you want with your resulting array of data. Or you could even use the async pipe in your template rather than subscribing in your component.
I am filtering an array of JSON objects, I want to return the found object based on the passed in id argument.
getClient(id) {
return this.http.get<any>(' http://localhost:3000/clients')
.pipe(
filter(client => client.idNumber === id)
);
}
The observer to the above is not triggered and does not receive any data.
getClient(id)
.subscribe(
(data) => {
// nothing here
}
)
The below implementation of getClient achieves what I want.
getClient(id) {
return this.http.get<any>(' http://localhost:3000/clients')
.pipe(
map(clients => clients.find(client => client.idNumber === id))
);
}
I would like to understand, am I using the filter operator the wrong way?
And how is my usage different from the one here
Correct, you are using the rxjs filter operator the wrong way.
Per the docs:
Filter items emitted by the source Observable by only emitting those that satisfy a specified predicate.
In your example, the client value that is getting sent to filter is the array of clients that your http request returned. Then, you are saying "don't emit anything unless client (which is actually an array and has no "idNumber" property is equal to id, which will always be false because unefined !== number
If you want to modify the , you need to use the map operator. The map operator takes what the observable emits, lets you do something (like filter or find in the array), and then return that modified data to subscribers.
Also, if you type your responses property, TypeScript will warn you about this. Instead of this.http.get<any> use this.http.get<Client[]> or some more appropriate type. You would be able to see at compile time that Client[] has no property idNumber.
I am trying to delete a property name from the array of object, it's working properly using filter API,
const users = [
{ name: 'Tyler', age: 28},
{ name: 'Mikenzi', age: 26},
{ name: 'Blaine', age: 30 }
];
const myProp = users.filter(function (props) {
delete props.name;
return true;
});
console.table(myProp);
const myProp2 = users.reduce((people, user) => {
console.log(people);
console.log(user);
delete user.name;
return people;
}, []);
console.log(myProp2);
The same example before I am trying complete using reduce API, However, it's not working as expected.
It's not working because your not pushing to the previous element (you are always returning the empty array). You need to change it to:
const myProp2 = users.reduce((people, user) => {
delete user.name;
people.push(user)
return people;
}, []);
Please note that is not the intended use for reduce though - map is the operation you are looking for:
const myProp2 = users.map(u=> ({age: u.age}));
You actually want to use map for this, because you are selecting a transormation of the data into a new object (similar to Select in SQL or LINQ)
const myProps = users.map(u=> ({age: u.age}))
Also although the filter method worked, this is actually abuse of the filter method. The filter method is supposed to remove elements from the array depending on a condition. Your method worked because you returned true (which removed no elements) but you modified the current value on each iteration.
This is bad practice because you will confuse the next person to look at your code, they will wonder why you used filter as a method to transform the data rather than map.
Also don't use reduce because reduce is an aggregation function intended to perform aggregate functions on objects. Since the number of elements you are returning will be the same, map is better for this.
Reduce would be better suited for if you wanted to find out the average,max,min,median age or the most popular name etc...
I am having an array of objects where all objects have the same keys except the last object. Think like array have values and to denote all these values as a whole I have a key I am pushing the key at last along with the values in the array.
homeTask is a list of object which is the values and homeTaskKey is the key to represent the homeTask
res.data.resultSet.homeTask.forEach(element => {
var singleEvent={
task:'',
taskDuration:'',
status:'',
};
singleEvent.task=element.task;
singleEvent.taskDuration=element.taskDuration;
singleEvent.status=element.status;
newEvents.push(singleEvent);
});
newEvents.push(res.data.resultSet.homeTaskKey);
addEvent(newEvents);
}
addEvent is props method of parent component where I am setting the array to state variable name as events which is array type.
When I iterate over events using map I want to skip the last object since it does not have keys like task, taskDuration and status. Therefore it won't give any problem when I fetch those values.
events.slice(0, events.length-1).map(<function>);
this will ignore the last element and all n-1 entries will be fed to map
UPDATE:
the array name is events not event therefore it should be events.length
You could still use map, but simply pop the last element off once the map completes. For example:
const newEvents = homeTask.map(({ task, taskDuration, status }) => ({
task, taskDuration, status
}))
newEvents.pop()
addEvent(newEvents)
Or just replace the last item with your taskkey, as you know the last item will be junk:
newEvents[newEvents.length - 1] = res.data.resultSet.homeTaskKey
Or just slice the array prior to mapping, and then push the taskKey at the end like you were doing. slice is safe to perform on your prop, as it shallow copies.
Or most importantly, ask yourself why you have this entirely weird data structure that has the key appended on the end. Perhaps rethink your data and not create this problem for yourself in the first place.
res.data.resultSet.homeTask.forEach((element,index) => {})
second param to function is index you, can use this to identify the second last element by comparing it with total length of array.
hmm you can try with this
res.data.resultSet.homeTask.forEach(element => {
if(!element.task)
return false;
...bla bla bla
}
The map() method creates a new array with the results of calling a function for every array element.
So it creates an array of same length from source array.
What you need is filter()
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
Something like this;
const tasks = res.data.resultSet.homeTask.filter((element) => {
const { task, taskDuration, status } = element;
return task && taskDuration && status;
});