Combining observables of array objects - javascript

How do you combine two or more observable of array i.e. Observable<Object[]>, Observable<Object[]> using rxjs in order to return one Observable<Object[]>?
forkJoin and merge are emitting the two Observable<Object[]> arrays independently.
getEmployeesLeavesByEmployeeNumber2(employeeNumber,afromDate,atoDate) {
const scenario1 = this.afs.collection(`${environment.FB_LEAVES}`, ref => {
let query: firebase.firestore.CollectionReference | firebase.firestore.Query = ref;
query = query.where("employeeNumber", "==", employeeNumber);
query = query.where("fromDate",">=",afromDate);
query = query.where("fromDate","<=",atoDate);
return query;
}).snapshotChanges()
.pipe(take(1))
.pipe(
map(changes => {
return changes.map(a => {
const data = a.payload.doc.data() as Leave;
data.docId = a.payload.doc.id;
return data;
})
})
).pipe(map(leaves => {
let leavesArr=leaves.filter(leave => leave.status!==environment.LEAVE_STATUS_DECLINED)
return leavesArr;
}));
const scenario2 = this.afs.collection(`${environment.FB_LEAVES}`, ref => {
let query: firebase.firestore.CollectionReference | firebase.firestore.Query = ref;
query = query.where("employeeNumber", "==", employeeNumber);
query = query.where("toDate","<=",afromDate);
query = query.where("toDate","<=",atoDate);
return query;
}).snapshotChanges()
.pipe(take(1))
.pipe(
map(changes => {
return changes.map(a => {
const data = a.payload.doc.data() as Leave;
data.docId = a.payload.doc.id;
return data;
})
})
).pipe(map(leaves => {
let leavesArr=leaves.filter(leave => leave.status!==environment.LEAVE_STATUS_DECLINED)
return leavesArr;
}));
const scenario3 = this.afs.collection(`${environment.FB_LEAVES}`, ref => {
let query: firebase.firestore.CollectionReference | firebase.firestore.Query = ref;
query = query.where("employeeNumber", "==", employeeNumber);
query = query.where("fromDate","<=",afromDate);
return query;
}).snapshotChanges()
.pipe(take(1))
.pipe(
map(changes => {
return changes.map(a => {
const data = a.payload.doc.data() as Leave;
data.docId = a.payload.doc.id;
return data;
})
})
).pipe(
filter(leave => {
return leave!==undefined;
})
);
return merge(scenario1,scenario2);
}
I am expecting a single observable of array but getting 2 i.e.
emp's leaves: [{…}]
assign.component.ts:198 leaves array length at assignment error 1
assign.component.ts:168 emp's leaves: (2) [{…}, {…}]
assign.component.ts:198 leaves array length at assignment error 2

I have got it to work using:
return forkJoin(scenario1,scenario2).pipe(map((arr) => [...arr[0],...arr[1]] ));

Related

Firestore pagination with react-redux

I try to add pagination using firebase and react redux-toolkit. I get the logic but having trouble using it with redux.
At first I wanted to set lastDoc in redux state but I got error since it is an object.
Then I changed the way and started to keep id of last document in the state. But then I can't get the firebase doc itself
const lastDocRef = firestoreDB.doc(`catalog/${publishedBooks.lastDocId}`)
is not same with
const lastDoc = snap.docs[snap.docs.length-1];
I appreciate any help how to solve this.
import { createSlice, createAsyncThunk, createEntityAdapter } from '#reduxjs/toolkit';
import firebaseService from 'app/services/firebaseService';
const firestoreDB = firebaseService.firestoreDB;
export const getPublishedBooks = createAsyncThunk('adminApp/publishedBooks/getPublishedBooks',
async (params, { dispatch, getState }) => {
const promise = firestoreDB
.collection('catalog')
.orderBy('lastPublish', 'desc')
.limit(10)
.get()
.then(snap => {
const lastDoc = snap.docs[snap.docs.length-1];
dispatch(setLastDocId(lastDoc.id));
let books = [];
snap.forEach(bookDoc => {
const id = bookDoc.id;
const data = bookDoc.data();
const lastPublish = data.lastPublish.toDate().toISOString();
books.push({ ...data, id, lastPublish });
});
return books;
})
.catch(error => {
return {}
});
const result = await promise;
return result;
}
);
export const getPublishedBooksNext = createAsyncThunk('adminApp/publishedBooks/getPublishedBooksNext',
async (params, { dispatch, getState }) => {
const { publishedBooks } = getState().adminApp;
const lastDocRef = firestoreDB.doc(`catalog/${publishedBooks.lastDocId}`)
const promise = firestoreDB
.collection('catalog')
.orderBy('lastPublish', 'desc')
.startAfter(lastDocRef)
.limit(10)
.get()
.then(snap => {
const lastDoc = snap.docs[snap.docs.length-1];
dispatch(setLastDocId(lastDoc.id));
let books = [];
snap.forEach(bookDoc => {
const id = bookDoc.id;
const data = bookDoc.data();
const lastPublish = data.lastPublish.toDate().toISOString();
books.push({ ...data, id, lastPublish });
});
return books;
})
.catch(error => {
return {}
});
const result = await promise;
return result;
}
);
const publishedBooksAdapter = createEntityAdapter({});
const initialState = publishedBooksAdapter.getInitialState({
lastDocId: null
});
export const {
selectAll: selectPublishedBooks,
selectById: selectPublishedBookById,
selectTotal: selectPublishedBooksTotal
} = publishedBooksAdapter.getSelectors(state => state.adminApp.publishedBooks);
const publishedBooksSlice = createSlice({
name: 'adminApp/publishedBooks',
initialState,
reducers: {
resetPublishedBooks: (state, action) => initialState,
setLastDocId: {
prepare: doc => {
const payload = doc
return { payload };
},
reducer: (state, action) => {
state.lastDocId = action.payload;
}
},
resetLastDocId: {
prepare: () => {
const payload = null
return { payload };
},
reducer: (state, action) => {
state.lastDocId = action.payload;
}
},
},
extraReducers: {
[getPublishedBooks.fulfilled]: publishedBooksAdapter.setAll,
[getPublishedBooksNext.fulfilled]: publishedBooksAdapter.upsertMany
}
});
export const { resetPublishedBooks, setLastDocId, resetLastDocId } = publishedBooksSlice.actions;
export default publishedBooksSlice.reducer;
lastDocRef only returns the doc reference. You need to get the actual doc itself.
const lastDocRef = await firestoreDB.doc(`catalog/${publishedBooks.lastDocId}`).get();
And you should use await instead of then-catch for more readble code.
export const getPublishedBooksNext = createAsyncThunk('adminApp/publishedBooks/getPublishedBooksNext',
async (params, { dispatch, getState }) => {
const { publishedBooks } = getState().adminApp;
try {
const lastDocRef = await firestoreDB.doc(`catalog/${publishedBooks.lastDocId}`).get();
const snap = await firestoreDB
.collection('catalog')
.orderBy('lastPublish', 'desc')
.startAfter(lastDocRef)
.limit(10)
.get()
const lastDoc = snap.docs[snap.docs.length-1];
let books = [];
dispatch(setLastDocId(lastDoc.id));
snap.forEach(bookDoc => {
const id = bookDoc.id;
const data = bookDoc.data();
const lastPublish = data.lastPublish.toDate().toISOString();
books.push({ ...data, id, lastPublish });
});
return books;
} catch (error) {
return {}
}
}
);
Edit: You can also save the lastDoc to redux then reference it later to avoid additional workload fetching for the lastDocRef.
export const getPublishedBooksNext = createAsyncThunk('adminApp/publishedBooks/getPublishedBooksNext',
async (params, { dispatch, getState }) => {
const { lastDocRef } = getState().adminApp; // get saved lastDoc
try {
const snap = await firestoreDB
.collection('catalog')
.orderBy('lastPublish', 'desc')
.startAfter(lastDocRef) // use it here.
.limit(10)
.get()
const lastDoc = snap.docs[snap.docs.length-1];
let books = [];
// dispatch(setLastDocId(lastDoc.id)); // instead of saving the doc id
dispatch(setLastDoc(lastDoc)); // save the last document instead
snap.forEach(bookDoc => {
const id = bookDoc.id;
const data = bookDoc.data();
const lastPublish = data.lastPublish.toDate().toISOString();
books.push({ ...data, id, lastPublish });
});
return books;
} catch (error) {
return {}
}
}
);

Optimize these 4 methods in 1 method

const getDate = (id) =>{
const userId = info.find(user => user.id === id)
return userId.date;
}
const getValueAtOne = (id) => {
const userId = info.find(user => user.id === id)
if(userId?.value[0]){
return userId?.value[0]
}
}
const getValueAtTwo = (id) => {
const userId = info.find(user => user.id === id)
if(userId?.value[1]){
return userId?.value[1]
}
}
const getAllValues = (id) => {
const userId = info.find(user => user.id === id)
if(userId?.value) {
const arrValue = userId?.roles.map((validData) => {
return { value: validData, label:validData };
});
return arrValue;
}
}
I have these 4 methods, which I am calling from different places in my code.But i want to optimize my code and want all these methods in a single method, but I cant figure out the best way to do it. first method returns me the date, second one returns the value of array at position 1, third method returns value of array at position 2, and 4th method returns the all the value and convert it in object.
You can return a single object like so:
const getAll = (id) => {
const userId = info.find((user) => user.id === id);
const [valueAtOne, valueAtTwo] = userId?.value ?? [];
const allValues =
userId?.value.roles.map((validData) => ({
value: validData,
label: validData,
})) ?? [];
return {
allValues,
valueAtOne,
valueAtTwo,
};
};

Compine Pipes in array with angular Observable

I have Observable where I'm piping it to multipe pipes as follows.
getOrders(filters: any, page: any = 1, orderBy: string = 'deliver_on', sort: string = 'asc') {
const opCityPipe = pipe(
filter((obj: any) => obj.payload.order.op_city_id === 1)
);
const storeRefPipe = pipe(
filter((obj: any) => obj.payload.order.store.ref.match(/^Your.*/))
);
const statusPipe = pipe(
filter((obj: any) => ['assign_request', 'accepted',
'in_store', 'en_route_to_client', 'arrived',
'canceled'].indexOf(obj.payload.order.status) !== -1)
);
return Observable.create((observer: Observer<any>) => {
this.socket.on('private-order.status.changed.1:order.status',
(obj: any) => {
observer.next(obj);
});
}).pipe(opCityPipe, storeRefPipe, statusPipe);
}
How can I make the pipe as array? I want to dynamically populate it. I tried to add the array but got an error.
ERROR TypeError: Object(...)(...) is not a function
I want to do something like
const pipes = [opCityPipe, storeRefPipe, statusPipe];
Observable.pipe(pipes);
UPDATE SOLUTION
Observable.pipe(...pipes);
I think this should work, but the error you're getting is not clear.
getOrders(filters: any, page: any = 1, orderBy: string = 'deliver_on', sort: string = 'asc') {
const opCityPipe = filter((obj: any) => obj.payload.order.op_city_id === 1);
const storeRefPipe = filter((obj: any) => obj.payload.order.store.ref.match(/^Your.*/));
const statusPipe = filter((obj: any) => ['assign_request', 'accepted',
'in_store', 'en_route_to_client', 'arrived',
'canceled'].indexOf(obj.payload.order.status) !== -1);
const filters = [opCityPipe, storeRefPipe, statusPipe]
return Observable.create((observer: Observer<any>) => {
this.socket.on('private-order.status.changed.1:order.status',
(obj: any) => {
observer.next(obj);
});
}).pipe(...filters);
}

remove duplication in object array from firebase or filter it? look code for more info

Here I validate if my users status is true, and if they are, I put them in an array. The thing here is that next time it will validate, all those who already was true will be added to the same array. Can it be solved by filter instead of push, or should I take the validation in any other way?
import {
UPDATE_LIST_SUCCESS
} from './types'
var arr = []
export const fetchList = () => {
return (dispatch) => {
firebaseRef.database().ref().child('users')
.on('value', snapshot => {
snapshot.forEach(function (child) {
var data = child.val()
if (child.val().profile.status === true) {
arr.push(data)
}
})
dispatch({ type: UPDATE_LIST_SUCCESS, payload: arr })
})
}
}
You can do it like this:
import {
UPDATE_LIST_SUCCESS
} from './types'
export const fetchList = () => {
return (dispatch) => {
firebaseRef.database().ref().child('users')
.on('value', snapshot => {
var arr = snapshot.filter(function (child) {
return child.val().profile.status === true
}).map(function (child) {
return child.val();
});
dispatch({ type: UPDATE_LIST_SUCCESS, payload: arr })
})
}
}
So here is my not so pretty way of solving it, but it works.
import {firebaseRef} from '../firebase/firebase'
import {
UPDATE_LIST_SUCCESS
} from './types'
export const fetchList = () => {
return (dispatch) => {
const arrayToFilter = []
firebaseRef.database().ref().child('users')
.on('value', snapshot => {
let snap = snapshot.val()
// Get acces to the keys in the object i got from firebase
let keys = Object.keys(snap)
// iterate the keys and put them in an User object
for (var i = 0; i < keys.length; i++) {
let k = keys[i]
let name = snap[k].profile.name
let age = snap[k].profile.age
let status = snap[k].profile.status
let profile_picture = snap[k].profile.profile_picture
let users = {name: '', age: '', status: Boolean, profile_picture: ''}
users.name = name
users.age = age
users.status = status
users.profile_picture = profile_picture
// adding the user object to an array
arrayToFilter.push(users)
}
// filter and creates a new array with users depending if their status is true
let arr = arrayToFilter.filter(child => child.status === true)
dispatch({ type: UPDATE_LIST_SUCCESS, payload: arr })
})
}
}

Create elements dynamically with CycleJS

I'm trying to learn some CycleJS and I ended up not knowing what to do exactly with this. The goal is to create inputs via configuration, instead of declaring them manually. The problem is I'm only getting rendered the last of the inputs from the array, instead of both. I'm assume the error is in view$ and how I'm dealing with the stream.My naive implementation is this:
Main.js
const sliderGunProps$ = xs.of({
value: 30,
id: 'gun'
});
const sliderCannonProps$ = xs.of({
value: 70,
id: 'cannon'
});
const propsConfig = [sliderGunProps$, sliderCannonProps$];
function view$(state$) {
return xs.fromArray(state$)
.map(state => {
return xs.combine(state.sliderVDom$, state.values)
.map(([sliderVDom, value]) =>
div([sliderVDom, h1(value)])
);
})
.flatten();
}
function model(actions$) {
return actions$.map((action) => {
const sliderVDom$ = action.DOM;
const sliderValue$ = action.value;
const values$ = sliderValue$.map(val => val);
return {
sliderVDom$: sliderVDom$,
values: values$
};
});
}
function intent(sources) {
return propsConfig.map(prop$ => Slider({
DOM: sources.DOM,
props$: prop$
}));
}
function main(sources) {
const actions$ = intent(sources);
const state$ = model(actions$);
const vdom$ = view$(state$);
const sink = {
DOM: vdom$
};
return sink;
}
Thanks!
I ended up figuring out how to solve it. The point was that I was not understanding how view$ handle the streams. The proper code:
function total(values) {
return xs.combine(...values)
.map(val => val.reduce((acc, x) => acc + x));
}
function view$(state$) {
const DOMElements = state$.map(slider => slider.sliderVDom$);
const values = state$.map(slider => slider.values);
const totalValue$ = total(values);
return xs.combine(totalValue$, ...DOMElements)
.map(([totalValue, ...elements]) => (
div([
...elements,
h1(totalValue)
])
));
}
function model(actions$) {
return actions$.map((action) => ({
sliderVDom$: action.DOM,
values: action.value.map(val => val)
}));
}
function intent(sources) {
return propsConfig.map(prop$ => Slider({
DOM: sources.DOM,
props$: prop$
}));
}

Categories