Rx: Aggregate IObservable<...> to IObservable<Unit> - system.reactive

I have an observable of type IObservable<...> and need to return from the function an observable of type IObservable<Unit>. Currently I do it as follows:
IObservable<Unit> DoSomething() {
var observable = SomeOtherMethodThatReturnsObservable();
// convert Convert IObservable<...> to IObservable<Unit>
var ret = new AsyncSubject<Unit>();
observable.Subscribe(_ => { }, ret.OnCompleted);
return ret;
}
Is there more nice way of doing so? Maybe extension method or something?

I would not use the example you gave in production, because it does not consider the following:
What happens if the Observable errors?
The error is eaten, and the consumer never knows the observable has ended.
What happens if the consumer disposes of it's subscription?
The underlying subscription is not disposed of
What happens if the consumer never subscribes?
The underlying observable is still subscribed to.
Typically, using the built in operators provide the best implementation.
Here's an "Rx-y" way of doing it.
source.IgnoreElements()
.Cast<Unit>()
.Concat(Observable.Return(Unit.Default));
Here's another a way to do the same thing: :)
without built-in-operators. This is arguably more efficient, but theres not real gain here.
// .Cast<Unit>()
Observable.Create<Unit>(o => { // Also make sure you return the subscription!
return source.Subscribe(
// .IgnoreElements()
_ => { },
// Make sure errors get passed through!!!
o.OnError,
// .Concat(Observable.Return(Unit.Default));
() => {
o.OnNext(Unit.Default);
o.OnCompleted();
});
});
Here's how to write it just once.
public static class ObsEx
{
public static IObservable<Unit> WhenDone<T>(this IObservable<T> source)
{
return Observable.Create<Unit>(observer =>
{
return source.Subscribe(
_ => { },
observer.OnError,
() => {
observer.OnNext(Unit.Default);
observer.OnCompleted();
});
});
}
}
And use it like so: source.WhenDone();

Related

How to navigate through a paginated list until find a text with Serenity-JS?

I need to assert that an element I created was added to a list, but it is added at the end of it and it is paginated.
I'm thinking of navigating through each page by calling another task this way:
export class CheckItem implements Task {
static afterCreated(): CheckItem {
return new CheckItem();
}
performAs(actor: PerformsTasks & UsesAbilities): PromiseLike<void> {
return TakeNotes.as(actor).read('item-name')
.then((itemName: string) => actor
.attemptsTo(
NavigateThroughItemList.untilFinds(itemName),
));
}
}
I need to implement NavigateThrougItemList.
Ok, I found the solution by using a recursive call. Maybe there is a better solution for this, but this one worked for me.
export class NavigateThroughItemList implements Task {
static untilFinds(itemName: string): NavigateThroughItemList {
return new NavigateThroughItemList(itemName);
}
constructor(private itemName: string) {
}
#step('{0} navigates through the list until #itemName is found')
performAs(actor: PerformsTasks): PromiseLike<void> {
return actor.attemptsTo(
See.if(Text.ofAll(ItemList.items), include(this.itemName)),
).catch(() => actor
.attemptsTo(
See.if(
WebElement.of(ItemList.nextButton),
isEnabled(),
),
)
.then(() => actor
.attemptsTo(
Click.on(ItemList.nextButton),
))
// Looping until it finds the item
.then(() => this.performAs(actor)));
}
}

RXJS Create Observable for sockets

I'm trying to return an Observable from a service that would allow you to listen to any Pusher channel.
Works fine, expect I'd like to unsubscribe from the pusher channel when the Observable (generated using createRealtimeObservable(...)) was unsubscribed from.
Any ideas?
public realtimeObservable (channelName, eventName): Observable<any> {
const realtimeObservable$ = new Observable((observer) => {
const channel = this.pusher.subscribe(`private-organization-${this.authService.userProfile.organization_id}-${channelName}`)
channel.bind(eventName, (data) => {
observer.next(data.payload)
})
})
return realtimeObservable$
}
When you manually create an Observable yourself it is your responsibility to cleanup all resources when unsubscribed to. Luckily the Observable has a mechanism for this by allowing you to return an unsubscribe/dispose function when creating:
return Observable.create((obs) => {
const channel = this.pusher.subscribe(`private-organization-${this.authService.userProfile.organization_id}-${channelName}`)
channel.bind(eventName, (data) => {
observer.next(data.payload)
});
return () => {
// unsubscribe event
channel.unsubscribe(); // NOTE: i do not know the syntax for unsubscribing a Pusher channel, impl as required.
};
});
NB: Observable.create() is syntactic sugar for new Observable()

Observable switch/flatMap fires instantly

I have the following code in my service:
public loginWithFacebook(): Observable<any> {
console.log('Login');
return Observable.fromPromise(this.fb.login()).flatMap((userData) => {
return this.http.post(authFacebook, {access_token: userData.authResponse.accessToken}, { observe: 'response' });
}).do( (response: HttpResponse<any>) => {
const token = response.headers.get('x-auth-token');
if (token) {
localStorage.setItem('id_token', token);
}
});
}
getFacebookProfile():Observable<any> {
console.log("Get Profile");
return Observable.fromPromise(this.fb.getLoginStatus())
.filter((state) => state.status === 'connected')
.switchMapTo(Observable.fromPromise(this.fb.api('/me')));
}
And later I use it in my component to get the profile info once login is successful.
this.profileData = this.usersService.loginWithFacebook()
.flatMapTo(this.usersService.getFacebookProfile());
However, for some reason getFacebookProfile() fires instantly even before the login procedure is complete. And I get an authentication error. Also, I have to login twice to get profile info displayed.
I've been always thinking that switchMap and flatMap only switch to the next observable once the previous one emits a value.
What am I doing wrong here?
--EDIT--
If I subscribe to the first Observable and call getFacebookProfile() in the subscription, everything works normally. But it's not very elegant solution I feel.
The problem is that promises are eager. You are calling this.fb.login() when you compose your observable and you are passing the returned promise into fromPromise.
That means that the login is initiated when loginWithFacebook is called and not when subscribe is called on the observable it returns.
If you want the login to be deferred until subscribe is called, you can use defer:
public loginWithFacebook(): Observable<any> {
console.log('Login');
return Observable.defer(() => Observable.fromPromise(this.fb.login()))
.flatMap((userData) => {
return this.http.post(authFacebook, {
access_token: userData.authResponse.accessToken
}, { observe: 'response' });
})
.do( (response: HttpResponse<any>) => {
const token = response.headers.get('x-auth-token');
if (token) {
localStorage.setItem('id_token', token);
}
});
}
For more information on using observables and promises, see Ben Lesh's article: RxJS Observable interop with Promises and Async-Await
It worked at last thanks to #cartant's answer. However, for some reason, I had to wrap it with defer operator twice. I would be thankful if someone could explain why it was necessary to do it. It's a bit weird.
public loginWithFacebook(): Observable<any> {
return Observable.defer(() =>
Observable.defer(() => this.fb.login()).flatMap((userData) =>
{
return this.http.post(authFacebook, {access_token: userData.authResponse.accessToken}, { observe: 'response' });
}).do( (response: HttpResponse<any>) => {
const token = response.headers.get('x-auth-token');
if (token) {
localStorage.setItem('id_token', token);
}
})
);
}
getFacebookProfile():Observable<any> {
return Observable.defer(() =>
Observable.defer(() => this.fb.getLoginStatus())
.filter((state) => state.status === 'connected')
.switchMapTo(Observable.fromPromise(this.fb.api('/me')))
);
}

Cannot read property 'subscribe' of undefined in nested calls in Angular 2

I want to subscribe in company-list.component on getCompanies() from the company.service. However I get the following error:
Cannot read property 'subscribe' of undefined
This is the code:
company.service.ts
getCompaniesOfUser() {
let r;
this.userService.getUser().subscribe(
res=> r = res,
err => console.log(err),
()=>(this.getCompaniesWithSpecificLink())
)
}
getCompaniesWithSpecificLink() {
if (isAdmin == false) {
url = '/user/companies';
} else {
url = '/companies';
}
return this.getCompanies(url);
}
getCompanies(url):Observable<Company[]> {
return this.http.get(this.host + url)
.map((res:Response) => res.json());
}
company-list.component.ts
companies:Company[];
public getTheCompanies() {
this._companyService.getCompaniesOfUser()
.subscribe(companies => this.companies = companies); <-- the error occurred here
}
Subscribe method must be used on an observable but your getCompaniesOfUser() method is not returning anything thus is a void method.
If you want to call back-to-back rest services. Like one's input depending on the others' output. You should use the flatMap operator.
Check this answer for example: https://stackoverflow.com/a/36712707/5706293
Something like this should work:
getCompaniesOfUser() {
return this.userService.getUser().flatMap( (resp) => {
return this.getCompaniesWithSpecificLink();
});
}

How to merge the output of an Observable that emits Observables without breaking the operator chain?

The context is using Couchbase to implement a REST CRUD service on a 2-level document store. The data model is an index document pointing to zero or more item documents. The index document is retrieved as an Observable using an asynchronous get. This is followed by a .flatMap() that retrieves zero or more IDs for each item document. The async get returns an Observable, so now the Observable I'm creating is Observable>. I want to chain a .merge() operator that will take "an Observable that emits Observables, and will merge their output into the output of a single Observable" to quote the ReactiveX documentation :) Then I will .subscribe() to that single Observable to retrieve item documents. The .merge() operator has a many signatures, but I can't figure out how to use it in a chain of operators as follows:
bucket
.async()
.get(id)
.flatMap(
document -> {
JsonArray itemArray = (JsonArray) document.content().get("item");
// create Observable that gets and emits zero or more
// Observable<Observable<JsonDocument>> ie. bucket.async().gets
Observable<Observable<JsonDocument>> items =
Observable.create(observer -> {
try {
if (!observer.isUnsubscribed()) {
itemArray.forEach(
(jsonObject) -> {
String itemId = ((JsonObject)jsonObject).get("itemid").toString();
observer.onNext(
bucket.async().get(itemId)
);
}
}
);
observer.onCompleted();
}
} catch (Exception e) {
observer.onError(e);
}
}
);
return items;
},
throwable -> {
// error handling omitted...
return Observable.empty();
},
() -> {
// on complete processing omitted...
return null;
}
)
.merge( ???????? )
.subscribe(
nextItem -> {
// do something with each item document...
},
throwable -> {
// error handling omitted...
},
() -> {
// do something else...
}
);
EDIT:
You probably guessed I'm a reactive newbie. The answer from #akarnokd helped me realise what I was trying to do was dumb. The solution is to merge the emissions from the items Observable<Observable<JsonDocument>> inside the document closure and return the result of that. This emits the resulting JsonDocuments from the flatMap:
bucket
.async()
.get(id)
.flatMap(
document -> {
JsonArray itemArray = (JsonArray) document.content().get("item");
// create Observable that gets and emits zero or more
// Observable<Observable<JsonDocument>> ie. bucket.async().gets
Observable<Observable<JsonDocument>> items =
Observable.create(observer -> {
try {
if (!observer.isUnsubscribed()) {
itemArray.forEach(
(jsonObject) -> {
String itemId = ((JsonObject)jsonObject).get("itemid").toString();
observer.onNext(
bucket.async().get(itemId)
);
}
}
);
observer.onCompleted();
}
} catch (Exception e) {
observer.onError(e);
}
}
);
return Observable.merge(items);
},
throwable -> {
// error handling omitted...
return Observable.empty();
},
() -> {
// on complete processing omitted...
return null;
}
)
.subscribe(
nextItem -> {
// do something with each item document...
},
throwable -> {
// error handling omitted...
},
() -> {
// do something else...
}
);
Tested and works :)
Due to expressive limits of Java, we can't have a parameterless merge() operator that can be applied on an Observble<Observable<T>>. It would require extension methods such as in C#.
The next best thing is to do an identity flatMap:
// ...
.flatMap(document -> ...)
.flatMap(v -> v)
.subscribe(...)
You can call toList() to collect all emitted items into one list. I've not tested it but what about something like this:
bucket.async()
.get(id)
.flatmap(document -> { return Observable.from((JsonArray)document.content().get("item")})
.flatMap(bucket::get)
.toList()
.subscribe(results -> /* list of documents */);