Hello everyone I am learning angular and firebase. So the idea is I fetch some ticket from my ticket collection and after fetching add some new property in it but the problem is when I receive half data my ordering function call (the function which is responsible for adding a property). or in simple word we can say I receive data in stream form.
get_ticket() {
console.log('have permission');
this.get_ticket_service
.get_ticket_company(this.user.company)
.subscribe((res) => {
console.log('get response');
this.unStructure_ticket = res.map((e) => {
return {
id: e.payload.doc.id,
item: e.payload.doc.data(),
};
});
this.odering_ticket(this.unStructure_ticket)
});
ordering function
odering_ticket(data) {
const info = [];
console.log('hi');
data.map((ticket) => {
if (ticket.item.seen == false) {
ticket.item.ticketID = ticket.id;
ticket.item.ticketType = 'new';
info.push(ticket.item);
} else if (
ticket.item.new == true &&
ticket.item.currently_handle_sme_ID == undefined
) {
ticket.item.ticketID = ticket.id;
ticket.item.ticketType = 'not assigned';
info.push(ticket.item);
} else if (
ticket.item.currently_handle_sme_ID == localStorage.getItem('uid') &&
!this.has_view_all_ticket_permission
) {
ticket.item.ticketID = ticket.id;
ticket.item.ticketType = 'assigned';
info.push(ticket.item);
} else if (
ticket.item.currently_handle_sme_ID != undefined &&
this.has_view_all_ticket_permission
) {
ticket.item.ticketID = ticket.id;
ticket.item.ticketType = 'assigned';
info.push(ticket.item);
}
});
console.log('end map');
return info;
}
service.ts
get_ticket_company(company) {
return this.firebase_store
.collection('Ticket', (ref) => ref.where('company', '==', company))
.snapshotChanges();
}
output
have permission ,
get response ,
hi ,
end map ,
get response ,
hi ,
end map,
I want to call my ordering function one's after I receive all data
You must try to use the last RxJs operator which will make your pipeline wait until the last emitted value.
this.get_ticket_service
.get_ticket_company(this.user.company).pipe(
tap((poRes) => {
console.log('get response');
if (this.unStructure_ticket === undefined) {
// Initialize your data structure.
this.unStructure_ticket = res.map((e) => {
return {
id: e.payload.doc.id,
item: e.payload.doc.data(),
};
});
} else {
// Add or concate the data received
// this.unStructure_ticket.push()//;
}
}),
last(), // Wait for the last emitted value.
tap((poRes) => {
this.odering_ticket(this.unStructure_ticket);
}),
).subscribe();
Example of how this should work, but not tested code.
Related
I have written a code in vue.js which creates a websocket connection and subscribe to a channel to get the data of BTC in USD.
But that channel one sends the updated data of size for some prices but I need a way to resubscribe to the channel to get the update prices as well.
Here is the link for bybit api documentation
Here is the code I tried:
function GetData(that) {
var ws_bybit_ita = new WebSocket("wss://stream.bybit.com/realtime");
ws_bybit_ita.onopen = function () {
ws_bybit_ita.send(
JSON.stringify({ op: "subscribe", args: ["orderBook_200.100ms.BTCUSD"] })
)
}
ws_bybit_ita.onmessage = function (msgEvent) {
let response = JSON.parse(msgEvent.data)
const data = response;
if (data.data && data.type == "snapshot") {
console.log(data.data);
} else if (data.type == "delta") {
}
CheckState(that,ws_bybit_ita)
};
function CheckState(that, ws_bybit_ita) {
if (ws_bybit_ita.readyState === 1) {
ws_bybit_ita.send(
JSON.stringify({ op: "unsubscribe", args: ["orderBook_200.100ms.BTCUSD"] })
)
ws_bybit_ita.close();
}
setTimeout(function () {
GetData(that);
}, 500);
};
I am trying to send an http.post request for each element of an array, my method works well, but when I subscribe, it does it for each of the requests, if someone could help me optimize this, I will I would really appreciate it, here I leave the snippets of my code.
component.ts
saveExclusion() {
this.indForm.value.Centers.forEach(element => {
for (const days of this.exclusionDays) {
delete days.horadesde;
delete days.horahasta;
delete days.id;
for (const key in days) {
if (days[key] === true) {
days[key] = true;
}else if (days[key] === false) {
delete days[key];
}
}
}
const valueForm = this.indForm.value;
valueForm.ResourceId = this.idResource;
valueForm.TimeZoneId = 'America/Santiago';
valueForm.CenterId = element;
this.exclusionFunc = false;
this.apiFca.saveNew(valueForm, this.exclusionDays)
.pipe(last()).subscribe((res: any) => {
console.log(res)
if (res === '200') {
this.successMessage = true;
this.exclusionDays = [];
this.indForm.reset();
this.ngOnInit();
setTimeout(() => {
this.successMessage = false;
}, 3000);
}
}, err => {
console.log('error', err);
});
});
}
service.ts
saveNew(exclusionData, daysBlock) {
let reason = '';
const dt = new Date();
const n = dt.getTimezoneOffset();
const tz = new Date(n * 1000).toISOString().substr(14, 5);
if (exclusionData.OtherReason) {
reason = exclusionData.ExclusionReason + ' ' + exclusionData.OtherReason;
} else {
reason = exclusionData.ExclusionReason;
}
if (exclusionData.ExclusionType !== 'Partial' ) {
daysBlock = [];
}
const data = {Exclusion: new ExclusionClass(
[],
reason,
exclusionData.ExclusionType,
exclusionData.Repetition,
exclusionData.CenterId,
exclusionData.ProfessionalName,
exclusionData.ResourceId,
daysBlock,
exclusionData.TimeZoneId,
'Exclude',
exclusionData.Unit,
exclusionData.ValidFrom + 'T' + exclusionData.ValidTimeFrom + ':00-' + tz,
exclusionData.ValidTo + 'T' + exclusionData.ValidTimeUntil + ':59.999-' + tz
)};
if (exclusionData.CenterId === '') {
delete data.Exclusion.CenterId;
}
return this.http
.post("url", data)
.pipe(
map((res: any) => {
return res.code;
})
);
}
greetings, and I look forward to your comments, thanks.
I'm not fully confident in my rxjs knowledge but it looks like, because of .pipe(last()), you are only watching the last request? I'd recommend you only set success if all completed without error, like
this.apiFca.saveNew(valueForm, this.exclusionDelays)
.subscribe(
res => {
console.log(res);
},
err => {
console.log(err);
},
() => {
this.successMessage = true;
// etc. etc. etc.
});
or maybe instead of using this.successMessage use something like this.saveState$ that would be the a BehaviorSubject object initialized with 'idle' (or some enum thereof) that your saveExclusion() function manages. That way, the beginning of your saveExclusion() function could
set const saveState$ = this.saveState$
assert that saveState$.getValue() === 'in process' or if not, do something about it,
saveState$.next('in process');
and you could change your subscribe line to
this.apiFca.saveNew(valueForm, this.exclusionDelays)
.subscribe(
res => {
if (res !== '200') {
saveState$.next('unexpected result')
} },
err => {
console.log(err);
saveState$.next('error');
},
() => {
if (saveState$.getValue() === 'in process') {
saveState$.next('success');
} }
);
And then you can subscribe to your component's saveState$ as well (though outside of the component you'd want to provide saveState$.asObservable() so values can't be injected by outside code). This affords some elegant event-driven code in your component initialization:
saveState$.pipe(filter(val => val === 'error'))
.subscribe(functionToTellYourUserThereWasAnError);
// if successful, we want other code to know, but immediately change it back to 'idle' even if other code errors
saveState$.pipe(filter(val => val === 'success')
.subscribe(val => saveState$.next('idle'));
// upon success, reset me
saveState$.pipe(filter(val => val === 'success'))
.subscribe(
val => {
this.exclusionDays = [];
// etc. etc.
// setTimeout not needed because set to 'idle' in a different thread.
}
)
Plus, I think your template could reflect and change the UI in response to changes in saveState$ as well, so your save button can be enabled/disabled based on whether or not saveState is 'idle', etc.
I'm using an rxjs epic as a middleware for an async action in a react-redux app.
I'm trying to simulate an ajax request (through a dependency injection) and test the behavior of this epic based on the response.
This is my epic :
export const loginEpic = (action$, store$, { ajax }) => { // Ajax method is injected
return action$.ofType(LoginActions.LOGIN_PENDING).pipe(
mergeMap(action => {
if (action.mail.length === 0) {
return [ loginFailure(-1) ]; // This action is properly returned while testing
} else {
return ajax({ ... }).pipe(
mergeMap(response => {
if (response.code !== 0) {
console.log(response.code); // This is logged
return [ loginFailure(response.code) ]; // This action is expected
} else {
return [ loginSuccess() ];
}
}),
catchError(() => {
return [ loginFailure(-2) ];
})
);
}
})
);
};
This part test if the mail adress is empty and works just fine (Or at least just as expected):
it("empty mail address", () => {
testScheduler.run(({ hot, expectObservable }) => {
let action$ = new ActionsObservable(
hot("a", {
a: {
type: LoginActions.LOGIN_PENDING,
mail: ""
}
})
);
let output$ = loginEpic(action$, undefined, { ajax: () => ({}) });
expectObservable(output$).toBe("a", {
a: {
type: LoginActions.LOGIN_FAILURE,
code: -1
}
});
});
});
However, I have this second test that fails because the actual value is an empty array (There is no login failed returned):
it("wrong credentials", () => {
testScheduler.run(({ hot, cold, expectObservable }) => {
let action$ = new ActionsObservable(
hot("a", {
a: {
type: LoginActions.LOGIN_PENDING,
mail: "foo#bar.com"
}
})
);
let dependencies = {
ajax: () =>
from(
new Promise(resolve => {
let response = {
code: -3
};
resolve(response);
})
)
};
let output$ = loginEpic(action$, undefined, dependencies);
expectObservable(output$).toBe("a", {
a: {
type: LoginActions.LOGIN_FAILURE,
code: -3
}
});
});
});
Any idea on what I'm doing wrong / why this part returns an empty array (The console.log does actually log the code):
if (response.code !== 0) {
console.log(response.code);
return [ loginFailure(response.code) ];
}
While this part returns a populated array:
if (action.mail.length === 0) {
return [ loginFailure(-1) ];
}
I'm guessing the use of Promise is causing the test to actually be asynchronous. Try changing the stub of ajax to use of(response) instead of from
I am building a filter section where I am fetching data through my API and setting up a new state ‘[result]’. The process is simple since data are fetched on click event and with certain condition. Data are stocked in an array since the user is able to filter several input checked.
My goal is to remove a particular index when I unchecked a checkbox input and if array is empty then reset my state to the data it fetched when the page first loaded.
I heard lodash could help me building my feature though I don't know how to implement on my react-app.
How could I build this process ?
Here’s my code :
handleFilterButtons = event => {
var filterIcons = event.target.getAttribute("name");
var valueInput = event.target.value;
var apiFilters = `https://backend.greatsaigon.com/api/v1/en/directories/${
this.state.directory
}/venues`;
axios
.get(apiFilters)
.then(response => {
let result;
let prevResult = [];
let toggleButton;
response.data.forEach(req => {
if (filterIcons === "Hair") {
if (req.options.beauty.hair.treatment !== "0") {
req.options.beauty.hair.treatment.forEach(res => {
if (res === valueInput && toggleButton !== undefined) {
result = req;
this.state.stockResult.unshift(result);
}
});
}
} else if (filterIcons === "Nails") {
if (req.options.beauty.nails.treatment !== "0") {
req.options.beauty.nails.treatment.forEach(res => {
if (res === valueInput) {
result = req;
this.state.stockResult.unshift(result);
}
});
}
} else if (filterIcons === "Spa & Massage") {
if (req.options.beauty.spa.treatment !== "0") {
req.options.beauty.spa.treatment.forEach(res => {
if (res === valueInput) {
result = req;
this.state.stockResult.unshift(result);
}
});
}
}
});
this.setState({
result: _.uniq(this.state.stockResult),
isToggle: toggleButton
});
})
.catch(function(error) {
console.log(error);
});
};
I have the following piece of code. As is, with a couple of lines commented out, it works as expected. I subscribe to a stream, do some processing and stream the data to the client. However, if I uncomment the comments, my stream is always empty, i.e. count in getEntryQueryStream is always 0. I suspect it has to do with the fact that I subscribe late to the stream and thus miss all the values.
// a wrapper of the mongodb driver => returns rxjs streams
import * as imongo from 'imongo';
import * as Rx from 'rx';
import * as _ from 'lodash';
import {elasticClient} from '../helpers/elasticClient';
const {ObjectId} = imongo;
function searchElastic({query, sort}, limit) {
const body = {
size: 1,
query,
_source: { excludes: ['logbookType', 'editable', 'availabilityTag'] },
sort
};
// keep the search results "scrollable" for 30 secs
const scroll = '30s';
let count = 0;
return Rx.Observable
.fromPromise(elasticClient.search({ index: 'data', body, scroll }))
.concatMap(({_scroll_id, hits: {hits}}) => {
const subject = new Rx.Subject();
// subject needs to be subscribed to before adding new values
// and therefore completing the stream => execute in next tick
setImmediate(() => {
if(hits.length) {
// initial data
subject.onNext(hits[0]._source);
// code that breaks
//if(limit && ++count === limit) {
//subject.onCompleted();
//return;
//}
const handleDoc = (err, res) => {
if(err) {
subject.onError(err);
return;
}
const {_scroll_id, hits: {hits}} = res;
if(!hits.length) {
subject.onCompleted();
} else {
subject.onNext(hits[0]._source);
// code that breaks
//if(limit && ++count === limit) {
//subject.onCompleted();
//return;
//}
setImmediate(() =>
elasticClient.scroll({scroll, scrollId: _scroll_id},
handleDoc));
}
};
setImmediate(() =>
elasticClient.scroll({scroll, scrollId: _scroll_id},
handleDoc));
} else {
subject.onCompleted();
}
});
return subject.asObservable();
});
}
function getElasticQuery(searchString, filter) {
const query = _.cloneDeep(filter);
query.query.filtered.filter.bool.must.push({
query: {
query_string: {
query: searchString
}
}
});
return _.extend({}, query);
}
function fetchAncestors(ancestorIds, ancestors, format) {
return imongo.find('session', 'sparse_data', {
query: { _id: { $in: ancestorIds.map(x => ObjectId(x)) } },
fields: { name: 1, type: 1 }
})
.map(entry => {
entry.id = entry._id.toString();
delete entry._id;
return entry;
})
// we don't care about the results
// but have to wait for stream to finish
.defaultIfEmpty()
.last();
}
function getEntryQueryStream(entriesQuery, query, limit) {
const {parentSearchFilter, filter, format} = query;
return searchElastic(entriesQuery, limit)
.concatMap(entry => {
const ancestors = entry.ancestors || [];
// if no parents => doesn't match
if(!ancestors.length) {
return Rx.Observable.empty();
}
const parentsQuery = getElasticQuery(parentSearchFilter, filter);
parentsQuery.query.filtered.filter.bool.must.push({
terms: {
id: ancestors
}
});
// fetch parent entries
return searchElastic(parentsQuery)
.count()
.concatMap(count => {
// no parents match query
if(!count) {
return Rx.Observable.empty();
}
// fetch all other ancestors that weren't part of the query results
// and are still a string (id)
const restAncestorsToFetch = ancestors.filter(x => _.isString(x));
return fetchAncestors(restAncestorsToFetch, ancestors, format)
.concatMap(() => Rx.Observable.just(entry));
});
});
}
function executeQuery(query, res) {
try {
const stream = getEntryQueryStream(query);
// stream is passed on to another function here where we subscribe to it like:
// stream
// .map(x => whatever(x))
// .subscribe(
// x => res.write(x),
// err => console.error(err),
// () => res.end());
} catch(e) {
logger.error(e);
res.status(500).json(e);
}
}
I don't understand why those few lines of code break everything or how I could fix it.
Your use case is quite complex, you can start off with building up searchElastic method like the pattern bellow.
convert elasticClient.scroll to an observable first
setup the init data for elasticClient..search()
when search is resolved then you should get your scrollid
expand() operator let you recursively execute elasticClientScroll observable
use map to select data you want to return
takeWhile to decide when to complete this stream
The correct result will be once you do searchElastic().subscribe() the stream will emit continuously until there's no more data to fetch.
Hope this structure is correct and can get you started.
function searchElastic({ query, sort }, limit) {
const elasticClientScroll = Observable.fromCallback(elasticClient.scroll)
let obj = {
body: {
size: 1,
query,
_source: { excludes: ['logbookType', 'editable', 'availabilityTag'] },
sort
},
scroll: '30s'
}
return Observable.fromPromise(elasticClient.search({ index: 'data', obj.body, obj.scroll }))
.expand(({ _scroll_id, hits: { hits } }) => {
// guess there are more logic here .....
// to update the scroll id or something
return elasticClientScroll({ scroll: obj.scroll, scrollId: _scroll_id }).map(()=>
//.. select the res you want to return
)
}).takeWhile(res => res.hits.length)
}