I've got this stream which shows/hides notifications:
this.subscription = this.notificationsApi.notifications$.pipe(
concatMap((event) => {
return of(event).pipe(
delay(450),
tap(() => {
this.notification = event;
this.isActive = true;
this.cd.markForCheck();
}),
delay(isDefined(event.showFor) ? event.showFor : this.hideAfter),
/// Pause here if you hover over the notification ///
tap(() => {
this.isActive = false;
this.cd.markForCheck();
}),
delay(450)
);
})
).subscribe(() => {});
What I would like to do is pause the stream when you hover over the notification and continue when you're no longer hovering over it:
<div (mouseover)="pause()" (mouseout)="continue()"></div>
This is where I can't seem to find a solution that works in this case. I'm assuming I have to use another 1-2 Subjects and then use switchMap depending on if you pause or continue but like I said I can't figure out how exactly.
I tried looking at this StackBlitz for switchMap pause/resume functionality but when I tried that approach it didn't show any notifications at all.
Any pointers?
Check this stackblitz interactive and this static viz example
The main trick was to wait at least for
notification show delay
and the next message on the stream
and let mouse ins and outs to add to the delay.
The magic inside the concatMap does that (at least, I think it does...)
To start, we take the notifications$ and concatMap a delay on it. Therefore each msg would be shown at least DELAY time
NOTE: pseudocode
notifications$.concatMap(msg =>
timer(DELAY)
.ignoreElements()
.startWith(msg)
)
Then we want the mouse to delay the delay
notifications$
.concatMap(msg =>
mouse$
.switchMap(isOver => { // < We re-delay on mouse state change
if (isOver) {
return empty() // < Do nothing when user hovers
}
return timer(DELAY); // < after DELAY -- take in next msgs
})
// we need only one completion event from this mouse$+ stream
.take(1)
// existing logic to turn delay stream into msg stream with delay
.ignoreElements()
.startWith(msg)
)
Finally, if next message comes after the DELAY -- we need to still listen to mouse hovers and delay by them
// store current msg index
let currentMsgIndex = -1;
notifications$
// store current msg index
.map((msg,i) => {
currentMsgIndex = i;
return msg;
})
.concatMap((msg, i) => {
// we listen to events from the mouse
return memMouse$
// if mouse pos changed -- reeval timeout
.switchMap(value => {
// do nothing on mouse in
if (value) {
return empty();
}
// until next msg comes in -- we're tracking mouse in/out
let nextMsgAwait$;
if (i == currentMsgIndex) {
// current msg is latest
nextMsgAwait$ = notifications$.pipe(take(1));
} else {
// we already have next msgs to show
nextMsgAwait$ = of(void 0);
}
// if mouse is away -- wait for
// - timer for TIMEOUT
// - and till new msg arrives
// until then -- user can mouse in/out
// to delay the next msg display
return forkJoin(
timer(TIMEOUT)
, nextMsgAwait$
);
}),
// we need only one completion event from this mouse$+ stream
.take(1)
// existing logic to turn delay stream into msg stream with delay
.ignoreElements()
.startWith(msg)
})
For better understanding please see above mentioned examples -- I've added some comments there.
Related
I have a case when I need to wait for element (advertising), if it's visible then needs to click it, but if element wasn't found after timeout then needs to keep executing a test.
How to handle the situation with Cypress ?
The way Cypress says to check for a conditional element is Element existence
cy.get('body').then(($body) => {
const modal = $body.find('modal')
if (modal.length) {
modal.click()
}
})
Most likely you put that at the top of the test, and it runs too soon (there's no retry timeoout).
You can add a wait say 30 seconds, but the test is delayed every time.
Better to call recursively
const clickModal = (selector, attempt = 0) => {
if (attempt === 100) return // whole 30 seconds is up
cy.get('body').then(($body) => {
const modal = $body.find('modal')
if (!modal.length) {
cy.wait(300) // wait in small chunks
clickModal(selector, ++attempt)
}
})
return // done, exit
}
cy.get('body')
.then($body => clickModal('modal'))
Intercept the advert
Best is if you can find the url for the advert in network tab, use cy.intercept() to catch it and stub it out to stop the modal displaying.
I tried the above solution, but seems that in some cases parameter $body could not contain necessary element, cause it was not loaded when we invoked cy.get('body'). So, I found another solution, using jQuery via Cypress, here is it:
let counter = 0;
const timeOut: number = Cypress.config('defaultCommandTimeout');
const sleep = (milliseconds) => {
const date = Date.now();
let currentDate = null;
do {
currentDate = Date.now();
} while (currentDate - date < milliseconds);
};
while (true) {
if (Cypress.$(element).length > 0 && Cypress.$(element).is(':visible')) {
Cypress.$(element).click();
break;
} else {
sleep(500);
counter = counter + 500;
if (counter >= timeOut) {
cy.log(elementName+ ' was not found after timeout');
break;
}
}
}
I have some Subject. And one Observer subscribed to it. How to omit all Observer invocations if it is already processing one?
var subject = new Subject();
var observer = {
next: x => {
//... some long processing is here
console.log('Observer got a next value: ' + x)
}
};
subject.subscribe(observer);
subject.next(0);
subject.next(1);// <-- if 0 value is not processed in the observer then skip it
subject.next(2);// <-- if 0 value is not processed in the observer then skip it
I of cause can introduce some flag, set it in Observer before execution and clear it after. And apply filter operator, like this:
var subject = new Subject();
var flag = true;
var observer = {
next: x => {
flag = false;
//... some long processing is here
console.log('Observer got a next value: ' + x)
flag = true;
}
};
subject.filter(() => flag).subscribe(observer);
subject.next(0);
subject.next(1);// <-- if previous value is not processed in the observer then skip it
subject.next(2);// <-- if 0 value is not processed in the observer then skip it
But I believe that exists more elegant and efficient way to achieve that.
Use the exhaustMap operator instead of trying roll your own backpressure. It is designed to ignore new events while waiting for the current one to complete.
const clicks = fromEvent(document, 'click');
const result = clicks.pipe(
exhaustMap((ev) => interval(1000).pipe(take(5))),
);
result.subscribe(x => console.log(x));
So I have a stream made of an array of strings.
const msgs = ['Searching...', 'Gathering the Data...', 'Loading the Dashboard...'];
const msgs$ = Observable.from(msgs);
I need to emit one of these messages sequentially every 3 seconds until dataFetched$ stream emits - which basically means all data arrived to page. I cannot cycle through them. Once the last message is seen but data hasn't arrived, it shouldn't change.
I can go over each string in msgs over time with RxJS zip function
const longInt$: Observable<number> = Observable.interval(3000);
const longMsgsOverTime$ = Observable.zip(msgs$, longInt$, msg => msg);
Then when the dataFetched$ stream emits I need to switch to shortInt$ which is
const longInt$: Observable<number> = Observable.interval(1500);
And continue displaying the loading messages until the last one is visible. Each message should only be seen once.
It's quite mind bending for me - I can tick some of the requirements but not make it fully working.
After many tries I arrived to conclusion that we need to wrap msgs array in a Subject to prevent cycling though all of them again after we switched from longInt$ to shortInt$.
===================EDIT=====================
Following's ggradnig's answer and code I concocted this ugliness (for debugging purposes):
setLoaderMsg(){
console.log('%c setting loader msg', 'border: 10px dotted red;');
const msgs$ = Observable.from(['Searching...', 'Gathering the Data...', 'Loading the Dashboard...', 'Something something...', 'yet another msg...', 'stop showing me...']),
shortInt$ = Observable.interval(250).pipe(
tap(v => console.log('%c short v', 'background: green;',v))
),
longInt$ = Observable.interval(3000).pipe(
tap(v => console.log('%c long v', 'background: red;',v)),
takeUntil(this.dataFetched$)
),
// interval$ = Observable.interval(3000).pipe(takeUntil(this.dataFetched$)).pipe(concat(Observable.interval(250))),
interval$ = longInt$.pipe(concat(shortInt$));
Observable.zip(msgs$, interval$, msg => msg)
// Observable.zip(msgs$, interval$)
.pipe(tap(v => {
console.log('%c v', 'background: black; border: 1px solid red; color: white;', v);
this.loaderMsg = v;
}))
.subscribe(
() => {}, //next
() => {}, //error
// () => this.loading = false //complete
() => {
console.log('%c complete', 'background: yellow; border: 2px solid red');
// this.loading = false;
}
);
}
Below is a screengrab of my chrome console to see what happens. The shortInt$ stream would log green message, as you can see it never happens, even though the dataFetched$ stream emitted (orange dashed border).
Below the "fetch data emit" log we should see green background messages that would signify that the shortInt$ stream is emitting values.
================================= 2nd EDIT ========================
Below is dataFetched$ observable:
fetchData(){
console.log('%c fetching now', 'border: 2px dashed purple');
// this.initLoader();
this.dataFetched$ = Observable.combineLatest([
this.heroTableService.getHeroTableData([SubAggs.allNetworks.key], AggFieldsMap.legends.name),
this.researchService.benchmark(this.getSubAggsForOverviewTbl()),
this.multiLineChartService.getEngagementOverTime(this.getSubAggsForNetworkEngagementsOverTimeTable())
]).pipe(
share(),
delay(7000),
tap(v => {
console.log('%c fetch data emit', 'border: 2px dashed orange', v);
})
);
const sub = this.dataFetched$.subscribe(([heroTableRes, benchmarkRes, netByEngRes]: [ITable[], IBenchmarkResponse, any]) => {
// this.loading = false; ==> moved to dashboard
//xavtodo: split this shit into .do() or .tap under each http request call
//hero table logic here
this.heroTableData = heroTableRes;
//////////////////////////////////////////////////////
//engagement-by-network-table
this.engagementByNetworkData = this.researchService.extractDataForEngagementByNetworkTable(benchmarkRes, this.getSubAggsForOverviewTbl());
//////////////////////////////////////////////////////
//network eng over time logic here MULTI LINE CHART
this.engagementMultiLineData = this.multiLineChartService.extractEngagementData(netByEngRes, this.getSubAggsForNetworkEngagementsOverTimeTable());
this.networks = this.multiLineChartService.getNetworks();
this.multilineChartEngagements = Util.getNetworksWithAltNames(this.networks);
this.publishedContentData = this.multiLineChartService.extractPublishedData(netByEngRes);
//combined multiline chart
this.multiLineChartData = {
publishedData: this.publishedContentData,
engagementData: this.engagementMultiLineData
};
});
this.addSubscription(sub);
}
Sounds like a task for takeUntil and concat.
The first observable is longInt$. It completes as soon as dataFetched$ emits. For this requirement, you can use takeUntil. It takes another observable as a parameter and once that observable emits, the source observable will complete.
From the docs:
Emits the values emitted by the source Observable until a notifier Observable emits a value.
So, the first step would be:
const longInt$: Observable<number> = Observable.interval(3000)
.pipe(takeUntil(dataFetched$));
Next, you want to concat your other observable, shortInt$. Concat will let the first observable complete, then let the second observable take over.
From the docs:
Creates an output Observable which sequentially emits all values from given Observable and then moves on to the next.
This would look something like this:
const longMsgsOverTime$ = Observable.zip(msgs$,
longInt$.pipe(concat(shortInt$)), msg => msg);
Note: If you are using RxJS 5 or lower, replace the pipe(..) statements with the corresponding operator. For example, .pipe(concat(...)) turns into .concat(...)
Here is an example on StackBlitz: https://stackblitz.com/edit/angular-mtyfxd
I have this code, and failing to understand why I am not getting inside the map function (where I have the comment "I AM NEVER GETTING TO THIS PART OF THE CODE"):
export const fiveCPMonitoringLoadEpic = (action$, store) =>
action$
.ofType(
FIVE_CP_MONITORING_ACTION_TYPES.LOAD_FIVE_CP_MONITORING_DATA_STARTED
)
.debounceTime(250)
.switchMap(action => {
const params = action.params;
const siteId = { params };
// getting site's EDC accounts (observable):
const siteEdcAccount$ = getSiteEDCAccountsObservable(params);
const result$ = siteEdcAccount$.map(edcResponse => {
// getting here - all good so far.
const edcAccount = edcResponse[0];
// creating another observable (from promise - nothing special)
const fiveCPMonitoringEvent$ = getFiveCPAndTransmissionEventsObservable(
{
...params,
edcAccountId: edcAccount.utilityAccountNumber
}
);
fiveCPMonitoringEvent$.subscribe(x => {
// this is working... I am getting to this part of the code
// --------------------------------------------------------
console.log(x);
console.log('I am getting this printed out as expected');
});
return fiveCPMonitoringEvent$.map(events => {
// I NEVER GET TO THIS PART!!!!!
// -----------------------------
console.log('----- forecast-----');
// according to response - request the prediction (from the event start time if ACTIVE event exists, or from current time if no active event)
const activeEvent = DrEventUtils.getActiveEvent(events);
if (activeEvent) {
// get event start time
const startTime = activeEvent.startTime;
// return getPredictionMeasurementsObservable({...params, startTime}
const predictions = getPredictionMock(startTime - 300);
return Observable.of(predictions).delay(Math.random() * 2000);
} else {
// return getPredictionMeasurementsObservable({...params}
const predictions = getPredictionMock(
DateUtils.getLocalDateInUtcSeconds(new Date().getTime())
);
return Observable.of(predictions).delay(Math.random() * 2000);
}
});
can someone please shed some light here?
why when using subscribe it is working, but when using map on the observable it is not?
isn't map suppose to be invoked every time the observable fires?
Thanks,
Jim.
Until you subscribe to your observable, it is cold and does not emit values. Once you subscribe to it, the map will be invoked. This is a feature of rxjs meant to avoid operations that make no change (= no cunsumer uses the values). There are numerous blog posts on the subject, search 'cold vs hot obserables' on google
In MQL4, I know how to set stopLoss and takeProfit.
However, I would like to do something else when such events actually take place.
Is there any event listener associated with such?
Unfortunately, there are no trade-events in MQL4.
However, it can be simulated as such ( logic-only-code, may not compile ):
#property copyright "No copyright, can be used freely, Joseph Lee"
#property link "https://www.facebook.com/joseph.fhlee"
int vaiTicketList[];
int start() {
int viIndex;
// -----------------------------------------------------------
// EVENT CHECK SECTION:
// Check vaiTicketList (populated in the previous cycle) to see if
// each of the (previously) open ticket is still currently open.
// -----------------------------------------------------------
for( viIndex=0; viIndex<ArrayRange(vaiTicketList,0); viIndex++) {
// Check if Ticket which was previously opened in the last
// cycle is no longer open now.
if(!OrderSelect( vaiTicketList[viIndex], SELECT_BY_TICKET ) ) {
// -----------------------------------
// EVENT CATEGORIZATION:
// -----------------------------------
// Handle possible events here:
// -- Close event: (OrderSelect( ticket, SELECT_BY_TICKET, MODE_HISTORY) == true)
if( OrderSelect(vaiTicketList[viIndex], SELECT_BY_TICKET, MODE_HISTORY) )
eventTrade_Closed( vaiTicketList[viIndex] );
// -- StopLoss ( Buy: When OrderClosePrice() <= OrderStopLoss(),
// Sell: When OrderClosePrice() >= OrderStopLoss() )
// -- TakeProfit (Buy: When OrderClosePrice() >= OrderTakeProfit(),
// Sell: When OrderClosePrice() <= OrderTakeProfit() )
// -- Expiration, Cancel, etc, etc
}
}
// -----------------------------------------------------------
// Store a list of all currently OPEN trade tickets into array.
// This is used to be compared in the next tick.
// -----------------------------------------------------------
ArrayResize( vaiTicketList, OrdersTotal() );
for ( viIndex=0; viIndex<OrdersTotal(); viIndex++) {
if(OrderSelect(viIndex, SELECT_BY_POS, MODE_TRADES)) {
vaiTicketList[viIndex] = OrderTicket();
}
}
// -----------------------------------------------------------
};
// ---------------------------------------
// This is the Trade Close event handler
// ---------------------------------------
bool eventTrade_Closed( int pviTicket ) {
bool vbIsEventBubble = true;
// Do something here to handle the event.
// FEATURE: vbIsEventBubble TRUE will allow event bubbles.
return( vbIsEventBubble);
}
bool eventTrade_otherPossibleEvents1() {};
bool eventTrade_otherPossibleEvents2() {};
bool eventTrade_otherPossibleEvents3() {};
bool eventTrade_otherPossibleEventsN() {};
Something along this line. Hope it helps.
you can use OrdersHistoryTotal() with a static variable to recognize this event. if this value is increased means that a position has closed.
No, there is no such direct event listener.
But:
we may create one such and test it's activation on an OnTick() event-bound handler basis.
void OnTick(){ // MQL4 system-initiated event-handler
// ---
myOnTickStealthTP_EventMONITOR(); // my Event Monitor
myOnTickStealthSL_EventMONITOR(); // my Event Monitor
// ---
// other code
}
Extending, upon not2qubit's conjecture ( irrespective how on-topic, weak or wrong one might consider that ):
You just posted an artificial non-existing function. What good is that? It would have been far more helpful if you could have provided as partially working code snippet for what you suggest. Recalling that most users of MQL4 are not programmers. – not2qubit 47 mins ago
void myOnTickStealthTP_EventMONITOR(){ // HERE put everything,
// TP_Event // what the "something else"
// ( when such events
// actually take place
// )
// meant - that's fair, isn't it ?
...
}
void myOnTickStealthSL_EventMONITOR(){ // HERE put everything,
// SL_Event // what the "something else"
// ( when such events
// actually take place
// )
// meant - that's fair, isn't it ?
...
}