I'm trying to create observable stream which takes user id from cookie and, if not found in cookie, fetches it from API. How can I do it in RxJS?
var userIdRequest = Rx.Observable.bindCallback(generateIdAsync);
var cookieUserIdStream = Rx.Observable.of(getCookieValue("user_id"))
.filter(x => x !== null);
var userIdStream = cookieUserIdStream.__ifEmptyThen__(userIdRequest()); // <<< ???
// Emulating async request for user id
// Will be a JSONp call in real app
function generateIdAsync(cb) {
setTimeout(() => {
cb(`id_${new Date().getTime()}`);
}, 300);
}
function getCookieValue(name) {
var regexp = new RegExp(`${name}=([^;]*)`);
var match = document.cookie.match(regexp);
return match && match[1];
}
There's a defaultIfEmpty method which works with simple values only, not with observables. In Bacon.js there's or method for streams, which works perfectly fine, but I don't see anything similar in RxJS. Do I miss something or do I need to implement a custom observer?
You may concat the 2 observables and get the first emitted value:
var userIdStream = Rx.Observable.concat(cookieUserIdStream, userIdRequest).first();
Related
I have a subject which emits a string value and the code is as below: when the components get initialized, the subjectTypeSubject is null. But there is another method in a component get subscribed to this observable where i set isLoading to true. Because the finalize is not getting called, the loading is always set to true. How to make it work so it gets completed when the value is null as well.
private subjectTypeSubject = new BehaviorSubject<string>(null);
private getPage() {
this.subjectTypeSubject.pipe(
filter((selectedSubjectType) => {
console.log('subject type', selectedSubjectType); //first time it comes as null. so it wont go inside switchmap.
return selectedSubjectType && selectedSubjectType !== '';
}),
switchMap((selectedSubjectType) => {
return this.customListsService
.getCustomListItemsByTypeName()
}),
map((customItemsData) => {
return customItemsData
})
);
}
private _getPage(pageNumber: number, search: string) {
this.loading = true;
this._pageSubscription = this.getPage({
pageSize: this._config.pageSize,
pageNumber,
search
})
.pipe(finalize(() => (this.loading = false))) //this is not called
.subscribe((p) => {
this._currentPage = p.pageNumber;
this.options = p.options;
this._allLoaded = p.isLast;
this.loading = false;
});
}
Adding a takeWhile() instead of filter worked for me. If there is any other better solution. please let me know. thanks
BehaviorSubject doesn't complete unless you complete it
There are multiple ways to call complete an observable in a pipe. take, takeWhile and takeUntil are some of them. Calling .complete on the BehaviorSubject is also an option.
But you should ask yourself: is this really what you want to achieve here? After completion it's not possible to pass any data to the subscription, even if the initial BehaviorSubject emits a new value.
One thing that this strange about your code: it should not work at all. In getPage() you are creating a new observable (by piping the BehaviorSubject), but you are not returning it. Therefore it should return undefined. It‘s also a little bit odd that you are using pipe in a function call. You should either declare the pipe during initialization or directly subscribe to a newly created observable.
I want combineLatest functional but for variable count of observables.
Something like:
// init combineLatest of three observables
[1, 2, 3]
// first observable produced new value "2"
[2, 2, 3]
// third observable ended
[2, 2]
// first observable produced new value "1"
[1, 2]
// new observable added
[2, 2, 4]
Is it possible in RxJS?
If I understand the problem right, the solution is pretty tricky for something that looks innocent.
I try to go step by step to explain a potential solution.
First of all we need understand that there are 3 different events that we need to manage:
the fact that one Observable completes
the fact that one Observable is added to the array which is given to combineLatest
the fact that a new array of Observables has to be passed to combineLatest, either because we are at the beginning of the processing (i.e. with the initial array) or because we have added a new Observable or because one Observable has completed
The second thing that we need to recognize is that we need to store the array of Observables we pass to combineLatest in a variable, otherwise we are not able to add or remove Obaservables from there.
Once these things are clear, we can build a solution in the form of a function that returns 2 things:
the Observable that we want to subscribe to and that should have the behavior that we are looking for
a Subject that we can use to communicate that we want to add a new Observable to the combineLatest function
The last point we need to recognize is that any time we change the list of Observable, either because we add or because we remove an Observable (because it completed), we need to run the combineLatest function with the new fresh list of Observables.
Now that all this has been clarified, this is the code of the function that returns an Observable which behaves as described
function dynamicCombineLatest(startingObservables: Observable<any>[]) {
// this is the variable holding the array of Observables
let observables = startingObservables;
// this is the array that contains the list of Observables which have been, potentially, transformed to emit
// immediately the last value emitted - this happens when a new Observable is added to the array
let observablesPotentiallyWithLastValueImmediatelyEmitted =
startingObservables;
// this is the variable holding the array of values last notified by each Observable
// we will use it when we need to add a new Observable to the list
const lastValues = [];
// this are the Subjects used to notify the 3 different types of events
const start = new BehaviorSubject<Observable<any>[]>(observables);
const add = new Subject<Observable<any>>();
const remove = new Subject<Observable<any>>();
let skipFirst = false;
// this is the chain of operations which must happen when a new Observable is added
const addToObservables = add.pipe(
tap({
next: (obs) => {
console.log("add");
// we need to make sure that the Observables in the list will immediately start to emit
// the last value they emitted. In this way we are sure that, as soon as the new added Observable emits somthing,
// the last value emitted by the previous Observables will be considered
observablesPotentiallyWithLastValueImmediatelyEmitted = observables.map(
(o, i) => {
return startWith(lastValues[i])(o);
}
);
// the new Observable is added to the list
observables.push(obs);
observablesPotentiallyWithLastValueImmediatelyEmitted.push(obs);
},
})
);
// this is the chain of operations which must happen when an Observable is removed
const removeFromObservables = remove.pipe(
tap({
next: (obs) => {
const index =
observablesPotentiallyWithLastValueImmediatelyEmitted.indexOf(obs);
console.log("remove");
// we simply remove the Observable from the list and it "last value"
observablesPotentiallyWithLastValueImmediatelyEmitted.splice(index, 1);
observables.splice(index, 1);
lastValues.splice(index, 1);
// we make sure that the Observables in the list will immediately start to emit with the last value they emitted
observablesPotentiallyWithLastValueImmediatelyEmitted = observables.map(
(o, i) => {
return lastValues[i] ? startWith(lastValues[i])(o) : o;
}
);
// we set that the first value of the new combineLatest Observable will be skipped
skipFirst = true;
},
})
);
// here we merge the 2 chains of operations so that both add and remove logic will be executed
// when the relative Subjects emit
merge(addToObservables, removeFromObservables).subscribe({
next: () => {
console.log("new start");
// we notify that a change in the Observable list has occurred and therefore we need to unsubscribe the previous "combineLatest"
// and subscribe to the new one we are going to build
start.next(observablesPotentiallyWithLastValueImmediatelyEmitted);
},
});
// this is where we switch to a new Observable, result of the "combineLatest" operation,
// any time the start Subject emits a new Observable list
const dynamicObservables = start.pipe(
switchMap((_observables) => {
const _observablesSavingLastValueAndSignallingRemove = _observables.map(
(o, i) =>
o.pipe(
tap({
next: (v) => {
// here we save the last value emitted by each Observable
lastValues[i] = v;
},
complete: () => {
// here we notify that the Observable has completed and we need to remove it from the list
remove.next(o);
},
})
)
);
console.log("add or remove");
// eventually this is the Observable created by combineLatest with the expected array of Observables
const _combineLatest = combineLatest(
_observablesSavingLastValueAndSignallingRemove
);
const ret = skipFirst ? _combineLatest.pipe(skip(1)) : _combineLatest;
skipFirst = false;
return ret;
})
);
// here we return the Observable which will be subscribed to and the add Subject to be used to add new Observables
return { dynamicObservables, add };
}
You can look at this stackblitz for an example.
Buffer and combine based on a key
Here's a slight variant of what you're asking for. It works just like mergeAll, only it keeps a buffer and emits the latest for any observable that have emitted so far.
The varient here is that you need to supply string keys for your values to get attached to. You should be able to see how to turn this into array indices if you so choose.
The reason I haven't done this with an array is because there's no much undefined behavior. For example, if the first observable completes and the second observable emits, your elements are all opaquely re-ordered.
Using keys returns control back to the caller, who can just use Object.keys() if they don't care about indices/labels for their data.
Here you are:
interface LabeledObservable<T> {
label: string,
stream: Observable<T>
}
interface CombinedLatest<T> {
[key:string]: T
}
function combineLatestAll<T>():
OperatorFunction<
LabeledObservable<T>,
CombinedLatest<T>
>
{
return source$ => defer(() => {
const buffer = {};
return source$.pipe(
mergeMap(({label, stream}) => stream.pipe(
map(v => {
buffer[label] = v;
return {...buffer};
}),
finalize(() => {
delete buffer[label];
})
))
);
});
}
Subject for new observables
If you like the idea of a subject you can use to inject new observables into your combineLatest operator, this still allows that. The only alteration needed is that you must supply unique labels for your observables. If you don't care about the labels, you can just use any ID generator pattern (Like incrementing a global id counter or something).
const startingObservables: Observable<any>[] = /*some observables */;
const add = new Subject<LabeledObservable<any>>();
add.pipe(
combineLatestAll()
).subscribe(console.log);
startingObservables.forEach((stream,i) => {
add.next({label: "" + i, stream});
});
I have read the tutorial from this link https://magarcia.io/2019/02/18/bloc-pattern-with-react-hooks
and i just dont understand how the search query to the API is triggered when _query.next is called with new search terms
see below code.
export class SearchBloc {
private _results$: Observable<string[]>;
private _query$ = new BehaviorSubject<string>('');
constructor(private api: API) {
**this._results$ = this._query$.pipe(
switchMap((query) => {
return observableFrom(this.api.search(query));
})
);**
get results$(): Observable<string[]> {
return this._results$;
}
}
const SearchInput = () => {
const searchBloc = useContext(SearchContext);
const [query, setQuery] = useState('');
useEffect(() => {
searchBloc.query.next(query);
}, [searchBloc, query]);
return (
<input
type="text"
name="Search"
value={query}
onChange={({ target }) => setQuery(target.value)}
/>
);
};
Assuming that searchblock was put in the context, and during input change the query which is a behaviour subject is assigned a new value with next();
how or why does the api query executes?
I guess I did not understand the line with
this._results$ = this._query$.pipe(
switchMap((query) => {
so maybe the question is, how did the pipe worked? did it create a method callback that will execute when next is called? and what is the assignment to result mean?
anyone that can help me make sense of it is greatly appreaciated.
Consider the following code:
It creates a stream of 5 numbers. Then it creates a second stream which is defined as a stream that has all the same numbers as the first one, only each number is incremented.
const numberStream$ = of(1,2,3,4,5);
const numbersPlus1$ = numberStream$.pipe(
map(v => v + 1)
);
numbersPlus1$.subscribe(console.log);
If you subscribe to numberStream$ you should expect to get 1,2,3,4,5.
If you subscribe to numbersPlus1$ you should expect to get 2,3,4,5,6.
Here we do the same thing with a Subject. Of course, unlike of(1,2,3,4,5), a subject lets you create a stream imperatively. Whenever I call .next on a subject, I'm saying "Make this value the next emission in this subject's stream."
const numberSubject$ = new Subject<number>();
const numbersPlus1$ = numberSubject$.pipe(
map(v => v + 1)
);
numbersPlus1$.subscribe(console.log);
numberSubject$.next(1);
numberSubject$.next(2);
numberSubject$.next(3);
numberSubject$.next(4);
numberSubject$.next(5);
I'm new to Dart and therefore having trouble with asynchronous programming. I'm trying to loop through a list of elements (let's call them ingredients for now) and query the database for recipes which contain the ingredient. To achieve this, I have a list 'ingredientsSelectedList' and pass it over to a future which is supposed to query the Firestore Database and add the result to the 'possibleRecipes' List. The problem is, that I can't figure out how to 'await' the for loop to finish, before returning the 'possibleRecipes' List. Everytime I run it, it returns an empty list. Hope I didn't make it too complicated and Thanks in advance for everyone that's taking the time to read this :)
PS: I have spent hours to find a solution to this online, but couldn't find anything.
Future searchRecipe(ingredients) async {
var possibleRecipes = []; //List to store results
for (int i = 0; i < ingredients.length; ++i) {
var currentIngredient = ingredients[i];
//now query database for recipes with current ingredient
var fittingRecipes = Firestore.instance
.collection('recipes-01')
.where('ingr.$currentIngredient', isEqualTo: true);
fittingRecipes.snapshots().listen((data) => data.documents.forEach((doc) {
possibleRecipes.add(doc['name']); //add names of results to the list
}));
}
return possibleRecipes; //this returns an empty list
}
Yes you can
Simply use this code
Future searchRecipe( List ingredients) async {
var possibleRecipes = []; //List to store results
ingredients.forEach((currentIngredient) async{
//you can await anything here. e.g await Navigator.push(context, something);
//now query database for recipes with current ingredient
var fittingRecipes = await Firestore.instance
.collection('recipes-01')
.where('ingr.$currentIngredient', isEqualTo: true);
fittingRecipes.snapshots().listen((data) => data.documents.forEach((doc) {
possibleRecipes.add(doc['name']); //add names of results to the list
}));
});
return possibleRecipes; //this returns an empty list
}
put this in default flutter project on click event someFunc() and see the magic
someFunc() async {
for(int xq in x) {
print("printing the loop value $xq");
await functionThatReturnsAFuture(xq);
}
}
functionThatReturnsAFuture(int x) async {
await Future.delayed(const Duration(seconds: 2), (){
setState(() {
// This call to setState tells the Flutter framework that something has
// changed in this State, which causes it to rerun the build method below
// so that the display can reflect the updated values. If we changed
// _counter without calling setState(), then the build method would not be
// called again, and so nothing would appear to happen.
_counter++;
});
});
print("printing the loop value $x");
}
I need to create an observable, which I can "pull" data from, to work with a pageable api. I can only fetch 100 items per request, I want to be able to use observable as a generator function (on which I can call .next() to issue a request to get next 100 items.
I can't unfortunately find a way to do it with Rx. I suppose it's possible using controlled observable or a subject. Can you guys show me an example.
this is what I've gotten so far:
function list(entityType, viewName, fetchAll = false) {
var skip = 0,
total = 0;
const subject = new Rx.Subject(),
response$ = subject
.takeWhile(() => skip <= total)
.startWith(skip)
.flatMap((skip) => fetchPagePromise(skip)),
next = () => subject.onNext(skip);
if (fetchAll) {
Rx.Observable.timer(100, 100).subscribe(() => next());
}
return {
data$: response$.map(response => response),
next: fetchAll === true ? undefined : next
};
function fetchPagePromise() {
let limit = 100,
obj = {
viewName, limit, skip
},
qs = objectToQueryString(obj);
return $http.get(`${apiBase}/api/data/${entityType}${qs}`).then((res) => {
total = res.data.Total;
skip += limit;
return res.data.Rows;
});
}
}
this kinda works like a generator. it returns an Observable and next handler. Whenever next is called it pulls next 100 items from api and pushes into the Observable. Also if there’s a third parameter fetchAll passed, then it will keep fetching data until there’s no more. What scares me though that there are 2 mutating vars in function's closure - skip and total, and I don't know if managing them like this in asynchronous/unpredictable environment is ok.
One of the things you generally want to avoid is trying to make Rx into a plain old event emitter. Usually it is an indicator when you try and just trigger Observables manually by passing around a Subjects observer interface.
You should ask yourself, where is my data coming from? What calls next(), what calls that, etc. After enough of these you will generally find that this will lead you to something that can be wrapped by an Observable directly rather than explicitly calling next(). Also, I think the fetchAll flag should really be kept externally. You are only making the interface confusing by essentially turning it into a void method just by passing in a flag.
So I would recommend refactoring like so:
Rx.Observable.prototype.lazyRequest = function(entityType, viewName, limit = 100) {
var source = this;
return Rx.Observable.create(obs => {
var response = source
//Skip is really just the (limit * index)
.map((x, i) => i * limit)
.flatMap((skip) => {
let obj = {viewName, skip, limit},
qs = objectToQueryString(obj);
//Handle promises implicitly
return $http.get(`${apiBase}/api/data/${entityType}${qs}`);
},
//Return this with our skip information
(skip, res) => {skip, res})
//Publish it so the stream get shared.
.publish();
//This will emit once once you are out of data
var stop = response.first(x => x.skip >= x.res.data.Total);
return new CompositeDisposable(
//Complete this stream when stop emits
response.takeUntil(stop)
//Downstream only cares about the data rows
.map(x => x.res.data.Rows)
.subscribe(obs),
//Hook everything up
response.connect());
});
}
Then you can use it like so:
//An example of a "starting point", a button click
//Update the rows every time a new event comes through
Rx.Observable.fromEvent($button, 'click')
.startWith(0) //Inject some data into the pipeline
.lazyRequest(entityType, viewName)
.subscribe(/*Do something with the returned rows*/);
//Get all of the rows, will keep hitting the endpoint until it completes
Rx.Observable.interval(100)
.lazyRequest(entityType, viewName)
//Gather all the values into an array and emit that.
.toArray()
.subscribe();