Angular wait until subscribe is done and give values to other function - javascript

i have this following function
file: subcategory.service.ts
getSubCategoriesById(inp_subCatid: String): Observable<any>{
this.getSubCategoriesList().snapshotChanges().pipe(
map(changes =>
changes.map(c =>
({ key: c.payload.key, ...c.payload.val() })
)
)
).subscribe(subCategories => {
subCategories.filter(function (subCat) {
return subCat.id == inp_subCatid;
});
});
and i´m calling the top function in the following file
file: subcategory.page.ts
this.SubCategoryService.getSubCategoriesById(subCatid).subscribe((subCategories: any) => {
this.subCat = subCategories ;
})
the problem what i got is i´m getting following error message:
ERROR TypeError: "this.SubCategoryService.getSubCategorysById(...) is undefined"
i want to get the data when there are loaded from the file "subcategory.service.ts"
hope someone can help me.

Your method should be like this:
getSubCategories(inp_subCatid: string): Observable<any> {
return this.getSubCategoriesList().snapshotChanges().pipe(
map(changes => changes.map(c =>
({ key: c.payload.key, ...c.payload.val() })
).filter((subCat) => subCat.id === inp_subCatid)
));
}
Then you will be able to use like this:
this.subCategoryService.getSubCategories(subCatid)
.subscribe(subCategories => this.subCat = subCategories);
If I'm interpreting correclty your methods, it seems to me that you're using firebase... if so, after you call this.yourService.getSubCategories(subCatid) for the first time, your subscription will remain active so that your subcategories will be updated for every change on the database, even if you change subCatid, the previous database query will be alive. To avoid it, I suggest that you take just one emission of snapshotChanges():
getSubCategories(inp_subCatid: string): Observable<any> {
return this.getSubCategoriesList().snapshotChanges().pipe(
// finish the subscription after receiving the first value
take(1),
map(changes => changes.map(c =>
({ key: c.payload.key, ...c.payload.val() })
).filter((subCat) => subCat.id === inp_subCatid)
));
}

Thanks a lot
what if i want to filter for a specific data ?? like for "id"
getSubCategoriesbyId(inp_subCatid): Observable<any>{
this.getSubCategoriesList().snapshotChanges().pipe(
map(changes =>
changes.map(c =>
({ key: c.payload.key, ...c.payload.val() })
)
)
).subscribe(subCategories => {
subCategories.filter(function (subCat) {
return subCat.id == inp_subCatid;
});
});
}
and then to get the filtered data back
this.yourService.getSubCategoriesbyId(subCatid)
.subscribe(subCategories => console.log(subCategories));

Related

RxJS - Keep a cache of the list and update the existing ones

I have a WebSocket connection, that does 2 things:
Sends on the first 'DATA' event the full List(i.e: 5 Items)
On each next 'DATA' it sends information about the updated ones only.
I want to take that stream, process it, keep a cache of the items and do the following:
Keep the existing list.
If a new event arrives, and is in the list, update that based on an id(This should be generic enough).
If it doesn't exist, add it to the list.
This is what I have done so far. Which isn't much. I am appending the items every time. Any help would be appreciated:
function createCachedList$<T extends WSMessage<T>>(observable$: Observable<T>) {
const INITIAL_STATE: any[] = [];
const [fromDataPackets$, fromNonDataPackets$] = partition(
observable$,
(value) => value.type === WSMessageType.DATA
);
const pickDataPacket = fromDataPackets$.pipe(
map((value: any) => value?.data),
scan((prevState, currState: any[]) => {
const nextState = R.uniq([...prevState, ...currState]);
return [...prevState, ...nextState];
}, INITIAL_STATE),
tap((data: any) => console.log('Data:', data)),
map((data: any) => ({ type: WSMessageType.DATA, data }))
);
return merge(pickDataPacket, fromNonDataPackets$);
}
export default createCachedList$;
Your code seems OK. scan is the operator I would use.
Probably you need to elaborate a bit the logic within scan. Something like this could help
scan((prevState, currState: any[]) => {
currState.forEach(m => {
const item = prevState.find(s => s.id === m.id);
if (item) {
Object.assign(item, m)
} else {
prevState.push(m)
}
});
return prevState;
}, INITIAL_STATE),

Promise inside Observable in Angular 7+

I have a problem that I can't solve.
In the ngOnInit event I observe the url parameter. This parameter corresponds to a folder in firebase-storage. That way when loading I get a list of folders and/or files inside that folder that is being informed and storing it inside listReferences variable which is of type Reference[].
Here is the code:
ngOnInit() {
this.route.params
.subscribe(params => {
this.getFiles(params.ref).subscribe(
(listReferences) => {
this.listReferences = listReferences;
}
);
}
);
}
getFiles(folder: string) {
return this.storage.ref('/' + folder).listAll()
.pipe(
map((data) => {
return data.items;
})
);
}
It turns out that for each item in the listReferences array I need to access the getDownloadUrl() or getMetadata() method which are promising and I am unable to retrieve the values for each item in the array. How should I proceed in this case? How best to do this?
Basically I am following the information contained in the reference guide.
https://firebase.google.com/docs/reference/js/firebase.storage.Reference
How about using ForkJoin as follows:
ngOnInit() {
this.route.params
.pipe(
mergeMap(x => this.getFiles(x.ref)),
mergeMap((listReferences: { getDownloadUrl: () => Promise<string> }[]) => {
return forkJoin(listReferences.map(x => x.getDownloadUrl()))
})
)
.subscribe(x => this.urls = x)
}

React hooks state undefined on handle function

I am creating a simple quiz with persisting response state and i am currently stuck trying to figure out why responseState is undefined in my handleAnswerClick function, this function is triggered much later if at all per click. By then the States should all be set.
const Question: React.FC<IProps> = (props) => {
const [responseState, setResponseState] = useState([]);
const [question, setQuestion] = useState<IStateProps>(props.id);
const [answers, setAnswers] = useState([]);
const [choice, setChoice] = useState();
const initialResponseState = () => {
const data = {
duration: '00:01:00',
examTakerProgress: 1,
question: props.id,
answer: '1',
}
axios.post(`${host}/api/responseState/`, data)
.then(() => {
console.log('Created the initial Response State');
})
.catch((err) => {
console.log(err);
})
}
const getData = async () => {
await axios.all([
axios.get(`${host}/api/question/${question}`),
axios.get(`${host}/api/responseState/examTakerProgress/1/question/${props.id}`)
])
.then(axios.spread((questionData, responseData) => {
if (!responseData.data.length > 0) {
initialResponseState();
}
setResponseState(responseData.data);
setQuestion(questionData.data);
setAnswers(questionData.data.answers);
setChoice(responseData.data.length > 0 ? responseData.data[0].answer : '');
setTimeout(() => {
props.toggleLoading();
}, 50);
}))
.catch(err => console.error(err));
};
useEffect(() => {
getData()
}, [])
const handleAnswerClick = async (number: number) => {
setChoice(number);
const data = {
duration: '00:01:00',
examTakerProgress: 1,
question: props.id,
answer: number,
}
await axios.put(`${host}/api/responseState/${responseState[0].id}/`, data)
.then((data) => {
console.log(data);
console.log('choice changed to :' + number);
})
.catch((err) => {
console.log(err);
})
}
if (props.loading) {
return <Loader/>
}
return (
<React.Fragment>
<h3>{question.label}</h3>
<p className='mb-3'>{question.description}</p>
<ListGroup as="ul">
{answers.map((answer, index) => {
if (answer.id === choice) {
return <ListGroup.Item key={answer.id} action active> <Answer key={index}
answer={answer}/></ListGroup.Item>
} else {
return <ListGroup.Item key={answer.id} action onClick={() => {
handleAnswerClick(answer.id)
}}><Answer key={index}
answer={answer}/></ListGroup.Item>
}
}
)}
</ListGroup>
</React.Fragment>
)};
Can someone explain me the reason why this is happening?
Based on this line
const [responseState, setResponseState] = useState({});,
the responseState is an object.
but in the handleAnswerClick function you are using it as an array ${responseState[0].id}.
Note that this actually works if the object has a 0 key but since you're getting undefined that is not the case.
Reasons why responseState is undefined.
The most obvious is that you set responseState to undefined
The only place you call setResponseState is in getData where you have setResponseState(responseData.data);, so please check if responseData.data isn't undefined.
You never get to call setResponseState and the initial state is an object but you try to get responseState[0].id
I'm not sure what data type you are handling, but if you defined const [responseState, setResponseState] = useState({}); with the default value of useState as {} and then try to access responseState[0].id, there is two things happening.
Either you have an object with number keys or you have an array, but if you have an array, you should declare the initial value as an array.
But this isn't the problem, the problem is that you might never get to the part where you call setResponseState(responseData.data); and then you will have responseState to be {} and when trying to do responseState[0] it will be undefined, and when calling responseState[0].id you will get an error saying that you cannot read id of undefined.
Ways to fix this
Debug your code, make sure that the api call returns what you are expecting and setResponseState(responseData.data); is called.
Check if responseState[0] exists before try to access responseState[0].id
I would be able to help more, but you didn't add more information, so it's hard to help, but above is what I think it's the problem
Basically i am creating a responseState when a question loads for the first time, if a question is afterwards reloaded the State is already there since it is persisted. This means i also have to make sure to call setResponseState for the first case where the ResponseState still does not exist.
const initialResponseState = () => {
const data = {
duration: '00:01:00',
examTakerProgress: 1,
question: props.id,
answer: '1',
}
axios.post(`${host}/api/responseState/`, data)
.then(() => {
setResponseState(res.data)
console.log('Created the initial Response State');
})
.catch((err) => {
console.log(err);
})}

Emmit old value if SwitchMap has error in Rxjs

I am trying to get a member value from other web api in rxjs.
I did this in a pipe method with switchMap. But if there is a problem with getting the member value then I want to skip old model with values to next method.
So I dont want to return null after switchMap worked.
Here is my code:
(this.repository.getData(`employeecards/list/${this.currentUser.companyId}`) as Observable<EmployeeCard[]>)
.pipe(
flatMap(emp => emp),
tap(emp => emp),
switchMap((empCard: EmployeeCard) => this.repository.getData(`cards/${empCard.cardId}`),(empCard, card) => ({ empCard, card }) ),
//second subscribe
switchMap((emp: { empCard: EmployeeCard, card: LogisticCard }) => this.parraApiService.getBalanceByBarcode(emp.card.barcode),
(emp: { empCard: EmployeeCard, card: LogisticCard }, apiRes: ParraApiResult) => {
if (apiRes.response.isSuccess) {
//I use this subscribe only set to balance
emp.empCard.balance = apiRes.response.data['balance'];
}
return emp.empCard
}),
catchError((e) => {
return of([]); //I want to retun emp value
}),
reduce((acc, value) => acc.concat(value), [])
)
How can I solve this problem?
Thanks
I think you can achieve that by creating a closure:
(this.repository.getData(`employeecards/list/${this.currentUser.companyId}`) as Observable<EmployeeCard[]>)
.pipe(
flatMap(emp => emp),
tap(emp => emp),
switchMap((empCard: EmployeeCard) => this.repository.getData(`cards/${empCard.cardId}`), (empCard, card) => ({ empCard, card })),
switchMap(
(emp: { empCard: EmployeeCard, card: LogisticCard }) => this.parraApiService.getBalanceByBarcode(emp.card.barcode)
.pipe(
map((emp: { empCard: EmployeeCard, card: LogisticCard }, apiRes: ParraApiResult) => {
if (apiRes.response.isSuccess) {
emp.empCard.balance = apiRes.response.data['balance'];
}
return emp.empCard
}),
catchError((e) => {
// `emp` available because of closure
return of(emp);
}),
)
),
reduce((acc, value) => acc.concat(value), [])
)
Also notice that I gave up on switchMap's custom resultSelector, as it can easily be replaced with a map operator.

how to create only one subscribe() of this code

I've made this code that do its work, using RxJS:
from(dateQuery.first())
.subscribe((result) => {
query.greaterThanOrEqualTo('createdAt', result.createdAt);
const dateFormat = moment(result.createdAt).format('DD/MM/YYYY - HH:mm:ss');
from(query.find())
.map(el => el.map((e) => {
return {
id: e.id,
username: e.get('username')
}
}))
.mergeMap((array) => Observable.of({
names: array,
lastUpdate: dateFormat
}))
.subscribe(
(next) => res.send(serialize(next)),
(error) => res.send(serialize(error)),
() => console.log('completed'));
},
(error) => console.log(error)
);
My question: is it possible to create only one subscription instead of this two? Because I need to use result of the first subscription also in the other mergeMap before and I don't know how to store it if I try to do only one subscription.
Thank you for help.
You can do something like that
from(dateQuery.first()).pipe(
tap(() => query.greaterThanOrEqualTo('createdAt', result.createdAt)),
map(result => moment(result.createdAt).format('DD/MM/YYYY - HH:mm:ss')),
mergeMap(dateFormat => from(query.find()).pipe(
map(el => el.map((e) => ({
id: e.id,
username: e.get('username')
}))),
mergeMap((array) => Observable.of({
names: array,
lastUpdate: dateFormat
})),
tap(() => res.send(serialize(next))),
catchError(error => of('Find error'))
))
).subscribe();
Here's my first idea from what I understood.
I have exported the first subscription in an observable and I reuse it to construct the second observable (find$) where I do the second subscription.
I don't know what query.greaterThanOrEqualTo('createdAt', result.createdAt) does, so I put it in a .do() operator as a side effect. If you need to execute this method before executing the remaining code, return an observable or a promise from it and use a mergeMap as well.
const dataQueryFirst$ = dateQuery.first();
const find$ = dataQueryFirst$
.do((result) => query.greaterThanOrEqualTo('createdAt', result.createdAt))
.mergeMap(result => {
const dateFormat = moment(result.createdAt).format('DD/MM/YYYY - HH:mm:ss');
return from(query.find())
.map(el => el.map((e) => {
return {
id: e.id,
username: e.get('username')
}}))
.mergeMap((array) => Observable.of({
names: array,
lastUpdate: dateFormat
}))
});
find$.subscribe(
(next) => res.send(serialize(next)),
(error) => res.send(serialize(error)),
() => console.log('completed')
);
I would implement it with a "switchMap" .
Plus i would extract some logic in functions to make it more readable. And i prefere the new "pipe" way of rxjs since v5.5.
from(dateQuery.first()).pipe(
// TAP because it seems to be a sideeffect. If this could stop your stream, "filter" would be appropriate
tap(result =>query.greaterThanOrEqualTo('createdAt', result.createdAt)),
// MAP because we can discard "result" and only need the Moment-Object in the rest of the stream
map(result => getMoment(result.createdAt),
// switchMap to switch to the second stream, because it is only one command (in a long pipe) you don´t need a explicit return-command
switchMap( createdAt =>
from(query.find()).pipe(
// MAP because we now want a UserObjects-Array in our stream
map(el => createUserObjects(el)),
// MAP because we want our NamesUpdateList. Depending on the usage you may not need "Observable.of()" here
map(userObjects => createNamesUpdateList(userObjects, createdAt ))
)
)
).subscribe(
(next) => res.send(serialize(next)),
(error) => res.send(serialize(error)),
() => console.log('completed')
)
getMoment(date){
return moment(result.createdAt).format('DD/MM/YYYY - HH:mm:ss')
}
createUserObjects(el){
el.map((e) => {
return {
id: e.id,
username: e.get('username')
}
}
}
createNamesUpdateList(array, dateFormat){
return {
names: array,
lastUpdate: dateFormat
}
}
warm regards

Categories