I have the following code to obtain two observables in one and be able to manipulate them.
public products: any;
ngOnInit() {
this.products = this.productService.products().snapshotChanges().map(productSnaps => {
return productSnaps.map(product => {
const productData = product.payload.doc.data();
const productId = product.payload.doc.id;
return this.productService.getProductImages(productId).snapshotChanges().map(uploadSnap => {
let number = 0;
return uploadSnap.map(upload => {
if(number == 0) {
number++;
return upload.payload.doc.data();
}
})
})
.map(uploads => {
return {productId, ...productData, uploads: uploads};
})
})
})
.flatMap(products => Observable.combineLatest(products));
}
The services of products() and getProductImages() are the following:
type productsCollection = AngularFirestoreCollection<Product[]>;
products(): productsCollection {
return this.afs.collection<Product[]>('products');
}
getProductImages(productId: string) {
return this.afs.doc<Product>(`products/${productId}`).collection('uploads');
}
I have a base in firebase with the following structure:
my estructur of firebase
But it does not compile me with rxjs 6, I tried to do it like this:
ngOnInit() {
this.products = this.productService.products().snapshotChanges().pipe(map(productSnaps => {
return productSnaps.map(product => {
const productData = product.payload.doc.data();
const productId = product.payload.doc.id;
return this.productService.getProductImages(productId).snapshotChanges().pipe(map(uploadSnap => {
let number = 0;
return uploadSnap.map(upload => {
if (number === 0) {
number++;
return upload.payload.doc.data();
}
});
}),
map(uploads => {
return {productId, ...productData, uploads: uploads};
})
);
});
})
).flatMap(products => Observable.combineLatest(products));
}
But mark the flatMap and the combineLatest error
What at the end of accounts I need is that in the variable products both the documents of the collection "products" are stored as well as the collection that is inside each document and that contains the images of these.
and be able to use them like this:
<img height="250" *ngIf="product.uploads[0]" mat-card-image [src]="product.uploads[0].url" />
<mat-card-title>{{ product.name }}</mat-card-title>
tanks for you help!.
Related
The problem I'm having is, that I have a useContext in which I provide all logged users. On the initial run of the app or when the users' log in the array gets populated with all the users that are currently on the server... Which works as expected. But I have also the functionality, that whenever the server "user-connected" event runs, the front-end should just push the user to the end of this array. And there lays the problem. From the backend, the right user is sent, but when I access the connectedUsers array, the array is empty... but it should be already populated.
UsersProvider.tsx
export const inState = {
connectedUsers: [],
addUser: (user: any) => {},
sortUsers: (user: any, socketID: string) => {},
setLoggedUsers: () => {},
};
export interface initState {
connectedUsers: any[];
addUser(user: any): void;
sortUsers(users: any, socketID: string): void;
setLoggedUsers: React.Dispatch < React.SetStateAction < any[] >> ;
}
const UsersContext = createContext < initState > (inState);
export const useUsers = () => {
return useContext(UsersContext);
};
const initUserProps = (user: any) => {
user.messages = [];
user.hasNewMessages = false;
};
export const UsersProvider = ({
children
}: Props) => {
const [connectedUsers, setLoggedUsers] = useState < any[] > ([]);
const addUser = (user: any) => {
console.log('add', connectedUsers);
// This is empty, but it should be already populated when next user connected.
};
const sortUsers = (users: any, socketUserID: string) => {
const usersCopy = users;
usersCopy.forEach((u: any) => {
for (let i = 0; i < usersCopy.length; i++) {
const existingUser = usersCopy[i];
if (existingUser.userID === u.userID) {
existingUser.connected = u.connected;
break;
}
}
u.self = u.userID === socketUserID;
initUserProps(u);
});
// put the current user first, and sort by username
let sorted = usersCopy.sort((a: any, b: any) => {
if (a.self) return -1;
if (b.self) return 1;
if (a.username < b.username) return -1;
return a.username > b.username ? 1 : 0;
});
setLoggedUsers([...sorted]);
};
return ( <
UsersContext.Provider value = {
{
connectedUsers,
setLoggedUsers,
addUser,
sortUsers
}
} >
{
children
} <
/UsersContext.Provider>
);
};
And the part of ChatBoard.tsx, you can find addUser function initiated whenever user-connected happens. I really don't know why the would array be empty, if it is populated on the first run with users event.
const ChatBoard = (props: Props) => {
const socket = useSocket();
const {
connectedUsers,
setLoggedUsers,
addUser,
sortUsers
} = useUsers();
useEffect(() => {
if (socket == null) return;
socket.on('users', (users) => {
console.log(users);
if (socket.userID) {
const socketID: string = socket ? .userID;
sortUsers(users, socketID);
}
});
socket.on('user-connected', (user: any) => {
console.log(user, 'this user connected!');
const connectingUser = user;
addUser(connectingUser);
});
socket.on('user-disconnected', (userID) => {
console.log('disconnected user');
const users = [...connectedUsers];
users.forEach((u) => {
if (u.userID === userID) {
u.connected = false;
setLoggedUsers([...users]);
}
});
});
return () => {
socket.off('users');
socket.off('user-connected');
};
}, [socket]);
CodeSandbox
So I have found the problem... so with React hooks sometimes a problem occurs called "Stale Closures", which means that React was picking up the old state (empty one, the one that was not yet populated and always returning that one.).
The solution to this problem, in my case is that when you use setState you use it with a callback. Like so, so you always get the latest state.
const addUser = (user: any) => {
setLoggedUsers((oldUsers) => {
const newUsers: any[] = [...oldUsers];
console.log(newUsers);
for (let i = 0; i < newUsers.length; i++) {
const existingUser = newUsers[i];
if (existingUser.userID === user.userID) {
existingUser.connected = true;
return newUsers;
}
}
initReactiveProperties(user);
newUsers.push(user);
return newUsers;
});
};
My code looks something like this:
this.cashRegisterService
.query({
'storeName.contains': event.query,
'identifier.contains': event.query,
})
.subscribe(data => {
this.cashregisters = (data.body || []).map(cashRegister => ({
id: cashRegister.id,
name: `${cashRegister.identifier} ${cashRegister.storeName} (${cashRegister.mallName})`,
}));
});
I want to check if the identifier OR the storeName contain the event.query but what this does is to check if both of them contains the event.query. How can i do that? the method is generated by jhipster
query(req?: any): Observable<EntityArrayResponseType> {
const options = createRequestOption(req);
return this.http.get<ICashRegister[]>(this.resourceUrl, { params: options, observe: 'response' });
}
export const createRequestOption = (req?: any): HttpParams => {
let options: HttpParams = new HttpParams();
if (req) {
Object.keys(req).forEach(key => {
if (key !== 'sort') {
options = options.set(key, req[key]);
}
});
if (req.sort) {
req.sort.forEach((val: string) => {
options = options.append('sort', val);
});
}
}
return options;
};
I'm working on a project with graphs and I need to be able to cancel my requests if the user selects a different tab.
Here's my API call
export const getDifferentialData = (
sourceId: string,
sourceLine: string,
source: any
) => {
const graph1Request = getData(
sourceId,
sourceLine,
source
)
const graph2Request = getData(
sourceId,
sourceLine,
source
)
return Promise.all([graph1Request, graph2Request]).then(results => {
const [graphA, graphB] = results
return {
graphA: parsedData(graphA),
graphB: parsedData(graphB),
}
})
}
export const getData = (
sourceId: string,
sourceLine: string,
source?: any
) => {
if (sourceId && sourceLine) {
return api.get(`apiGoesHere`, { cancelToken: source.token }).then(response => {
const { data } = response
return parsedData(data)
})
} else {
return api.get(`apiGoesHere`, { cancelToken: source.token }).then(response => {
const { data } = response
return parsedData(data)
})
}
}
And the component where I'm doing the call. userDidChangeTab is called when pressing on a tab and it calls fetchGraph
const Graph: FC<Props> = () => {
const source = axios.CancelToken.source();
// we ensure that the query filters are up to date with the tab selected
const userDidChangeTab = (tabIndex: number) => {
const isDifferentialTabSelected = isDifferentialTab(tabIndex)
let newFilters = queryFilters
if (isDifferentialTabSelected) {
newFilters = {
// props go here
}
} else {
newFilters = {
// props go here
}
}
source.cancel()
fetchGraph(isDifferentialTabSelected)
setActiveTab(tabIndex)
}
// Function to fetch two differential graphs.
const fetchGraph = (isDifferential: boolean) => {
setFetching(true)
if (isDifferential) {
getDifferentialData(
sourceId,
sourceLine,
source
)
.then(({ graphA, graphB }: any) => {
setGraphData(graphA)
setMatchData(new diffMatch(graphA, graphB, 1.0))
})
.catch(reason => {
const errorMessage = errorMessageFromReason(reason)
addMessageToContainer(errorMessage, true)
})
.finally(() => {
setFetching(false)
})
} else {
getGraph(
sourceId,
sourceLine,
source
)
.then((graphData: any) => {
setGraphData(graphData)
setMatchData(null)
})
.catch(reason => {
const errorMessage = errorMessageFromReason(reason)
addMessageToContainer(errorMessage, true)
})
.finally(() => {
setFetching(false)
})
}
}
}
Problem with got data correctly execute function many one times, these function is execute in ngOnInit one time with abstraction but i dont know ocurrs these problem in a server, i thing in snapshotChanges but i don't know.
thx for help
https://i.stack.imgur.com/EinQg.png
return <Observable<Products[]>> t.db.collection(PATHS_FIRESTORE.products).snapshotChanges()
.pipe(
map(actions => {
let arr = actions.map((res) => {
let doc: any = <any>res.payload.doc.data()
let obj: any = {}
if (!isNullOrUndefined(cart)) {
for (const prod in cart) {
if (cart.hasOwnProperty(prod)) {
const element = cart[prod];
if (doc.uid === prod) {
obj[doc.uid] = {
name_product: doc.name_product,
path_img: doc.path_img,
price: doc.price,
quantity: doc.quantity + element.total,
uid: doc.uid,
uid_local: doc.uid_local
}
} else {
t.db.collection(PATHS_FIRESTORE.products).doc(prod).ref.get().then( res => {
const data = res.data()
return obj[res.id] = {
name_product: data.name_product,
path_img: data.path_img,
price: data.price,
quantity: element.total,
uid: doc.uid,
uid_local: doc.uid_local
}
})
}
}
console.log(obj)
}
return obj
}else {
obj = {
...doc
}
return obj
}
})
.filter((b: any) => {
return b.uid_local === uid_local
})
.filter((b: any) => {
return b.quantity > 0
})
.filter((b: any) => {
return !b.status
})
console.log(arr)
return arr
})
)
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]] ));