I know activatedRoute is actually BehaviourSubject, but since it's exposed API is observable, I don't want to take chances.
activatedRoute.queryParams.pipe(
take(1),
).subscribe(query => { // { page?: '1' | '2' | '3'... }
this.currentPage = new BehaviourSubject(+(query.page) || 1);
// Other components actually change this
});
console.log(this.currentPage); // I need this to not be undefined!
Does a pipe resembling this exists? takeSynchonously(1, { page: 1 })
I have found the answer, it is a combination of takeUntil, timer and queueScheduler.
const { BehaviorSubject, ReplaySubject, Subject, timer, queueScheduler } = rxjs;
const { startWith, defaultIfEmpty, takeUntil, take } = rxjs.operators;
new Subject().pipe(
take(1),
takeUntil(timer(1, queueScheduler)),
defaultIfEmpty(0),
).subscribe(n => console.log('Subject', n));
new ReplaySubject(1).pipe(
take(1),
takeUntil(timer(1, queueScheduler)),
defaultIfEmpty(0),
).subscribe(n => console.log('ReplaySubject', n));
const rs = new ReplaySubject(1);
rs.next(1)
rs.pipe(
take(1),
takeUntil(timer(1, queueScheduler)),
defaultIfEmpty(0),
).subscribe(n => console.log('ReplaySubject (with value)', n));
new BehaviorSubject(5).pipe(
take(1),
takeUntil(timer(1, queueScheduler)),
defaultIfEmpty(0),
).subscribe(n => console.log('BehaviorSubject', n));
console.log('priority')
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.5.2/rxjs.umd.js"></script>
Note: This must be used in combination with ReplySubject or similar. Anything with initial value will be synchronous, but those without it will be processed in the next tick.
Related
I'm new in rxjs world and I have to rewrite some code. So, I draft my ideas.
I have a request, which could fail and return an observable. I simulate that with the ob-variable and two map operations. Then, I try to catch an error. I need the result in my local variable selected and raise an event on isChanged. I call my function now via subscription. I don't need a result.
My question: Is one big pipe enough and can I use following approach for the work with my local variables?
import { of, map, Observable, tap, Subject, throwError, EMPTY } from 'rxjs';
import { catchError } from 'rxjs/operators';
let selected = 0;
const isChanged = new Subject<number>();
function myfunc(): Observable<boolean> {
const ob = of(1,3,4,5,7);
return ob.pipe(
// simulates a http request
map(v => v*2),
// simulates a rare error condition
map(v => {
// if (v === 8) { throw `four`; }
if (v === 10) { throw `ten`; }
return v;
}),
// play with different failure situations
catchError((e) => {
if (e === `four`) {
return of(4);
}
if (e === `ten`) {
return EMPTY;
}
console.warn(e);
return throwError(e);
}
),
// I need the result in a local variable
// I need a information about success
// I need the result not really
map((res) => {
selected = res;
isChanged.next(res);
return true;
})
);
}
console.log(`a: selected is ${selected}`);
isChanged.subscribe(v =>
console.log(`b: isChanged received: ${v}, selected is ${selected}`));
console.log(`c: selected is ${selected}`);
// I have to call the function
myfunc().subscribe((b) => {
console.log(`d: selected is ${selected}`);
});
I create the world in Stackblitz too:
https://stackblitz.com/edit/rxjs-6fgggh?devtoolsheight=66&file=index.ts
I see results like expected. But I'm not sure if all ideas are the right way to solve all problems.
Thanks for you thought.
I want to combine observables as is done with the "merge" operator but I still want to be able to know which input observable emitted, is there a way to do that?
For example:
private result$ = merge(this.obs1$, this.obs2$).pipe(
scan((result, change) => index + change, 0),
shareReplay(1)
);
Here both values from obs1 and obs2 will go into the "change" variable in the scan function whenever any of the input observables emit, but if I had access to a projector function where I could mark the values from the input observables with different names I could then do different things in the following scan function depending on which input observable emitted. Other operators like CombineLatest or ForkJoin do not seem to be applicable here either as they require completion or emits from all input observables.
If you need to keep track of which input observable emitted, then you may need to add metadata to your source observable. Without knowing the context of how result$ is used, this is the best possible solution with the information given.
I would suggest adding an id property to each observable you need to keep track of. Then, you can use some strategy in your scan operator based on the ID.
Below is a simple example using an id for each source observable. In the scan operator you will see how my strategy changes based on the ID.
import { interval, merge, of } from "rxjs";
import { map, scan, shareReplay } from "rxjs/operators";
const obs1$ = interval(1000).pipe(map(i => ({ i, id: "obs1" })));
const obs2$ = interval(3000).pipe(map(i => ({ i, id: "obs2" })));
let index = 0;
const result$ = merge(obs1$, obs2$).pipe(
scan((result, change) => {
if (change.id === "obs1") {
return index + change.i;
}
if (change.id === "obs2") {
return index + change.i * 2;
}
}, 0),
shareReplay(1)
);
result$.subscribe(console.log);
https://stackblitz.com/edit/rxjs-as5ket
The library #react-rxjs/utils has a util named mergeWithKey that can be used like this:
import { Subject } from "rxjs"
import { scan, startWith } from 'rxjs/operators'
import { mergeWithKey } from '#react-rxjs/utils'
const inc$ = new Subject()
const dec$ = new Subject()
const resetTo$ = new Subject<number>()
const counter$ = mergeWithKey({
inc$,
dec$,
resetTo$,
}).pipe(
scan((acc, current) => {
switch (current.type) {
case "inc$":
return acc + 1
case "dec$":
return acc - 1
case "resetTo$":
return current.payload
default:
return acc
}
}, 0),
startWith(0),
)
The implementation is pretty straight-forward:
import { merge, Observable, ObservableInput, from, SchedulerLike } from "rxjs"
import { map } from "rxjs/operators"
/**
* Emits the values from all the streams of the provided object, in a result
* which provides the key of the stream of that emission.
*
* #param input object of streams
*/
export const mergeWithKey: <
O extends { [P in keyof any]: ObservableInput<any> },
OT extends {
[K in keyof O]: O[K] extends ObservableInput<infer V>
? { type: K; payload: V }
: unknown
}
>(
x: O,
concurrent?: number,
scheduler?: SchedulerLike,
) => Observable<OT[keyof O]> = (input, ...optionalArgs) =>
merge(
...(Object.entries(input)
.map(
([type, stream]) =>
from(stream).pipe(
map((payload) => ({ type, payload } as any)),
) as any,
)
.concat(optionalArgs) as any[]),
)
Is this what you needed?
I have some events that I'd like to buffer but I'd like to buffer only after the first element.
[------bufferTime------]
Input over time:
[1, 2, 3, -------------|---4, 5, 6 ----------------]
Output over time:
[1]-----------------[2,3]---[4]------------------[5,6]
is there a way to do this?
I think this can be solved by dividing your stream into two, firstValue$ and afterFirstValue$, and then merging them.
import { merge } from 'rxjs';
import { take, skip, bufferTime } from 'rxjs/operators';
...
firstValue$ = source$.pipe(
take(1)
);
afterFirstValue$ = source$.pipe(
skip(1),
bufferTime(5000)
);
merge(firstValue$, afterFirstValue$)
.subscribe(result => {
// Do something
});
Answer to follow up question concerning subject
So I have done it so that the original source is a subject here. It is not exactly how you described it, but I think maybe this is what you want.
import { merge, Subject } from 'rxjs';
import { take, skip, bufferTime } from 'rxjs/operators';
import { Source } from 'webpack-sources';
...
source$ = new Subject();
firstValue$ = source$.pipe(
take(1)
);
afterFirstValue$ = source$.pipe(
skip(1),
bufferTime(5000)
);
merge(firstValue$, afterFirstValue$)
.subscribe(result => {
// Do something
});
source$.next(1);
source$.next(1);
source$.next(1);
You can use multicast to split the stream into two and just pass the first value through.
import { concat, Subject } from “rxjs”;
import { multicast, take, bufferCount } from “rxjs/operators”;
source.pipe(
multicast(
new Subject(),
s => concat(
s.pipe(take(1)),
s.pipe(bufferCount(X)),
)
),
);
I got really good answers that enlightened my view of the problem and made me come up with the real thing that I was needing, that was something like this:
function getLeadingBufferSubject (bufferTimeArg) {
const source = new Subject()
const result = new Subject()
let didOutputLeading = false
const buffered$ = source
.pipe(bufferTime(bufferTimeArg))
.pipe(filter(ar => ar.length > 0))
.pipe(map(ar => [...new Set(ar)]))
buffered$.subscribe(v => {
didOutputLeading = false
const slicedArray = v.slice(1)
// emits buffered values (except the first) and set flag to false
if (.length > 0) result.next(v.slice(1))
})
// emits first value if buffer is empty
source.subscribe(v => {
if (!didOutputLeading) {
didOutputLeading = true
result.next(v)
}
})
// call .next(value) on "source"
// subscribe for results on "result"
return {
source,
result
}
}
I had the same problem and after playing around with it, I found this additional solution:
source$.pipe(
buffer(source$.pipe(
throttleTime(bufferTime, asyncScheduler, {leading: true, trailing: true}),
delay(10) // <-- This here bugs me like crazy though!
)
)
Because throttle already features a leading option, you can just use it to trigger buffer emits manually.
I would really like to get rid of that delay here though. This is necessary because the inner observable is triggered first causing the buffer to emit prematurely.
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
I have a RXJS function that will create an empty Observable, tap into the result and return that new observable. I want the observable to always run the tap so I noop subscribe (in the real case it might not ever be subscribed to).
function that() {
let obs = of({});
obs = obs.pipe(tap(() => console.log('here')))
obs.subscribe();
return obs;
}
const res = that();
res.subscribe(() => console.log('finished'))
If you run this code on StackBlitz, you will notice that here is fired twice. The output looks like this:
here
here
finished
I've tried several different approaches but I can't ever seem to get it to work where it doesn't emit twice.
You subscribe TWICE:
function that() {
let obs = of({});
obs = obs.pipe(tap(() => console.log('here')))
obs.subscribe(); // first subscription
return obs;
}
const res = that();
res.subscribe(() => console.log('finished')) // second subscription
This is the same observable you subscribe to, once in the function, then on the returned value.
Just don't subscribe in the function
function that() {
let obs = of({});
obs = obs.pipe(tap(() => console.log('here')))
return obs;
}
const res = that();
res.subscribe(() => console.log('finished')) // subscribe from here only
See the updated StackBlitz.
Is it just a case of only tapping only the inner subscription?
function that() {
let obs = of({});
obs.pipe(tap(() => console.log('here'))).subscribe();
return obs;
}
const res = that();
res.subscribe(() => console.log('finished'))