I'm relatively new to the ReactiveX principles, but I'm a big fan so far of what I've learned. I have a challenge that I've put a little thought into, but I'd like to get a more experienced opinion on:
I have a few methods which return an Observable. Each one creates an Observable in a similar manor and then chains the same exact operators. Is there any way of abstracting those operators away such that I don't have to repeat this code each method.
For example, this is what I have right now:
public get(endpoint: string, options?: RequestOptions): Observable<any> {
return this.http.get(endpoint, requestOptions)
.map((response: Response) => {
let responseBody = response.json();
return responseBody;
})
.catch((error: Response) => {
let responseError = new ErrorResponse(error.status.toString(), error.json());
return Observable.throw(responseError);
})
.publishReplay()
.refCount()
.share();
}
public put(endpoint: string, body: any, options?: RequestOptions): Observable<any> {
return this.http.put(endpoint, body, requestOptions)
.map((response: Response) => {
let responseBody = response.json();
return responseBody;
})
.catch((error: any) => {
this.logger.logRequestError('PUT', endpoint, error);
return Observable.throw(error);
})
.publishReplay()
.refCount()
.share();
}
I'd like to take the map, catch, publishReplay, refCount, share operators and put them in their own operator, such that I can do something like this:
public get(endpoint: string, options?: RequestOptions): Observable<any> {
return this.http.get(endpoint, requestOptions).myOperator();
}
public put(endpoint: string, body: any, options?: RequestOptions): Observable<any> {
return this.http.put(endpoint, body, requestOptions).myOperator();
}
// define myOperator as something like:
.map((response: Response) => {
let responseBody = response.json();
return responseBody;
})
.catch((error: Response) => {
let responseError = new ErrorResponse(error.status.toString(), error.json());
return Observable.throw(responseError);
})
.publishReplay()
.refCount()
.share();
// end definition
Would something like this work for you? Using bind() you can defer the execution of your http call. I don't know which library you are using, if http.get() returns an Observable then you can just call it and pass the returned observable to handleHttpCall because Observables are (almost always) lazy and will not run code until subscribed upon.
public get(endpoint:string, options?: RequestOptions): Observable<any> {
return handleHttpCall(this.http.get.bind(endpoint, requestOptions));
}
private handleHttpCall(httpAction) {
return httpAction()
.map((response: Response) => {
let responseBody = response.json();
return responseBody;
})
.catch((error: Response) => {
let responseError = new ErrorResponse(error.status.toString(), error.json());
return Observable.throw(responseError);
})
.publishReplay()
.refCount()
.share();
}
Related
If you're using JS, the documentation works well. But in case of angular I would prefer to handle observables instead of promises. The problem is that this kind of promise has a handler. I tried many approaches listed below but nothing seems to work.
from(listen("click", v => v))
let x = async() => listen("click", v => v)
Does anyone know how to convert this kind of event to an Observable?
The response is always this:
function () {
var self = this,
args = arguments;
return new Promise(function (resolve, reject) {
var gen = fn.apply(self, args);
function _next(value) {
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value);
}
function _throw(err) {
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err);
}
_next(undefined);
});
}
You would have to create an Observable yourself with new Observable.
const obs$ = new Observable((subscriber) => {
const unlisten = listen("click", v => subscriber.next(v))
return async () => {
(await unlisten)()
}
})
Inside the callback, we listen to the events and pass each event to subscriber.next(v).
We also want to call unlisten when the Observable is unsubscribed to clean up the event listener. We can do that by returning the unlisten. The function returned by the callback will be called when the Observable is unsubscribed.
Thanks to #Tobias S., I was able to create those 2 functions and reuse them in all my services.
import {from, map, Observable, ObservableInput, ObservedValueOf} from "rxjs";
import {emit, listen, Event} from "#tauri-apps/api/event";
export function tauriListen(listenerName: string): Observable<any> {
return new Observable<any>((subscriber) => {
// return from(listen(listenerName, v => subscriber.next(v))).subscribe()
const unlisten = listen(listenerName, v => subscriber.next(v))
return async () => {
(await unlisten)()
}
}).pipe(
map((response: Event<any>) => response.payload)
);
}
export function tauriEmit(emitterName: string, payload: any) {
return from(emit(emitterName, payload));
}
I am failing to understand how to map the data properties out of HttpService in my NestJS application. To my understanding, this Observable just wraps axios. Here's some example code:
interface Todo {
task: string,
completed: false
}
import {
Injectable,
HttpService,
Logger,
NotFoundException,
} from '#nestjs/common'
import { map } from 'rxjs/operators
async getTodo(todoUrl: string): Todo {
const resp = this.httpService
.get('https://example.com/todo_json')
.pipe(map(response => response.data)) // map task/completed properties?
return resp
}
resp in this case seems to be of type Observable. How do I retrieve just the data properties I want using map on this request to return my Todo interface?
Nest by default will subscribe to the observable for you it your return the Observable from your service. As this can be the case you can do something like
#Injectable()
export class TodoService {
constructor(private readonly http: HttpService) {}
getTodos(todoUrl: string): Observable<Todo> {
return this.http.get(todoUrl).pipe(
map(resp => resp.data),
);
}
}
And so long as you have a controller class calling this.todoSerivce.getTodos(todoUrl) and returning it, the response will be sent out.
However, if you want to instead make it a promise as you are more accustomed to them, you can tack on a .toPromise() method to the observable chain and now it it awaitable (though it will be slower because it has to wait for the observable to emit its complete event).
Example with .toPromise():
#Injectable()
export class TodoService {
constructor(private readonly http: HttpService) {}
getTodos(todoUrl: string): Todo {
const myTodo = await this.http.get(todoUrl).pipe(
map(resp => resp.data),
).toPromise();
return myTodo;
}
}
Edit 1/20/22
In RxJS#^7, toPromise() is deprecated and will be removed in v8. Instead, you can use lastValueFrom to wrap the entire observable
#Injectable()
export class TodoService {
constructor(private readonly http: HttpService) {}
getTodos(todoUrl: string): Todo {
const myTodo = await lastValueFrom(this.http.get(todoUrl).pipe(
map(resp => resp.data),
));
return myTodo;
}
}
Looking at your code:
import { map } from 'rxjs/operators'
async getTodo(todoUrl: string): Todo {
const resp = this.httpService
.get('https://example.com/todo_json')
.pipe(map(response => response.data)) // map task/completed properties?
return resp
}
getTodo returns an Observable, not the response. So your return value should be Observable<Todo>.
The code should look more like this:
getTodo(): Observable<Todo> {
return this.http.get<Todo>('https://example.com/todo_json')
.pipe(
map(response => response.data),
catchError(this.handleError)
);
}
EDIT: You can't just return the data from this method because it is asynchronous. It does not have the data yet. The method returns an Observable ... which is basically a contract saying that it will (at some later time) return the data for you.
Async functions need to return a promise, you can call toPromise on an observable to return a promise.
async getTodo(todoUrl: string): Todo {
const resp = this.httpService
.get('https://example.com/todo_json')
.pipe(map(response => response.data)) // map task/completed properties?
return resp.toPromise();
}
async getTodo(todoUrl: string): Todo {
const resp = await this.httpService
.get('https://example.com/todo_json')
.toPromise();
return resp.data;
}
I have problem with my Angular. I have this functions:
private callUserInfo(): any {
this.isLoading = true;
return this._ajaxService.getService('/system/ping')
.map(
result => {
this.userId =
result.participant.substring(result.participant.indexOf('#'));
this.isLoading = false;
}
)
.catch(error => {
return Observable.throw(error);
});
}
public loadUserData(userName: string): any {
this.isLoading = true;
return this._ajaxService.getService('/User/' + userName)
.map(
result => {
const data = result[0];
this.user = new User(
data.id,
data.contacts[0].email,
data.name,
data.surname,
data.address.street,
data.address.city,
data.address.state,
data.address.country,
data.address.postCode,
data.address.timeZone);
this.isLoading = false;
})
.catch(error => {
return Observable.throw(error);
});
}
public getUser(): any {
if (this.user == null) {
this.callUserInfo().subscribe(() => {
this.loadUserData(this.userId).subscribe(() => {
return this.user;
});
});
} else {
return this.user;
}
}
In my component I call this service functions like this (auth service is service with functions defined up):
constructor(private _auth: AuthService) {
this.user = _auth.getUser();
}
But it stills return null (because Ajax calls are not finished?) Can someone explain me, how to call this two calls (first is system/ping service and based on return (userId) I need to call second ajax call (/user/id). After this two calls I have defined user in my service and I can return it to other components. Can someone expllain me, what am i doing wrong, or how I can do it better? I´m using newest version of angular.
P.S. Get service is from my wrapper service:
getService(url: string): Observable<any> {
return this.http
.get(this.base + url, this.options)
.map(this.extractData)
.catch(this.handleError);
}
You are not returning anything in case this.user==null
Change your function as following:
userObservabel=new BehaviourSubject(null);
public getUser(): any {
if (this.user == null) {
this.callUserInfo().subscribe(() => {
this.loadUserData(this.userId).subscribe(() => {
this.userObservabel.next(this.user);
});
});
return this.userObservabel.asObservable();
} else {
return this.userObservabel.asObservable();
}
}
and then you need to subscribe it
constructor(private _auth: AuthService) {
_auth.getUser().subscribe(user => this.user = user);
}
You need to call the second service in the subscribe or in the map method i.e. the Observable has returned a promise and that is resolved. Once that is resolved u should call your chained service.
A sample snipped from my POC might help you
this._accountListService.getAccountsFromBE().subscribe(
response => {
this.response = response;
this._accountListService.getAccountSorting().subscribe(
response => {
this.acctSort = response;
if (response.prodCode) {
this._accountListService.getAccountOrder().subscribe(
response => {
this.acctOrder = response;
this.response = this.setAccountOrder(this.response);
this.response.sort(this.myComparator);
this.acctFlag = true;
if (this.prodDesc) {
this.loader = false;
this.accountDetl = this.response[0];
this.accountDetl.entCdeDesc = this.prodDesc[this.accountDetl.entProdCatCde];
}
},
err => console.log(err)
);
}
},
err => console.log(err)
);
},
err => console.log(err)
);
I'm very new to Angular2!. We have a search method which create an async observable and executing it. When provide a valid input, it works good. But an invalid input is causing an Infinite loop. Please let me know if you see the issue.
Please see the code below.
const ngPromiseToObservable = <T>(p: ng.IHttpPromise<ng.IHttpPromiseCallbackArg<T>>): Observable<ng.IHttpPromiseCallbackArg<T>> => {
const o = new Subject();
p.catch((e) => o.error(e));
p.then((v) => o.next(v));
return o;
};
export class HttpObservable
{
public static $inject = [
"$rootScope",
"$http",
"HttpErrors",
];
constructor(
private $rootScope: ng.IScope,
private $http: ng.IHttpService,
private httpErrors: Subject<Object>
) {}
handleHttpResponseErrors<T>(o: Observable<T>): Observable<T>
{
return o.do(
_.noop,
(e: HttpResponseError) => this.httpErrors.next(e),
_.noop
);
}
applyAsyncOnAction<T>(o: Observable<T>): Observable<T>
{
let applyAsync: () => void = () => {
return this.$rootScope.$applyAsync();
};
return o.do(applyAsync, applyAsync, applyAsync);
}
// tslint:disable-next-line:no-any
post<T>(url: string, data: any, config?: Object): Observable<T>
{
return this.handleHttpResponseErrors(this.applyAsyncOnAction(ngPromiseToObservable(this.$http.post(url, data, config)).map((x) => x.data)));
}
// tslint:disable-next-line:no-any
put<T>(url: string, data: any, config?: Object): Observable<T>
{
return this.handleHttpResponseErrors(this.applyAsyncOnAction(ngPromiseToObservable(this.$http.put(url, data, config)).map((x) => x.data)));
}
get<T>(path: string): Observable<T>
{
return this.handleHttpResponseErrors(this.applyAsyncOnAction(ngPromiseToObservable(this.$http.get(path)).map((x) => x.data)));
}
Here a quite complex sample:
Main:
this.runInstructionAndGetResult().subscribe({
next: val => console.log(`NEXT VALUE: ${val}`),
error: val => console.log(`ERROR VALUE: ${val}`),
complete: val => console.log(`COMPLETE`)
});
Observables:
public runInstructionAndGetResult(): Observable<string> {
return this.runAnInstruction()
.flatMap((data) => {
console.info("flatMap of runAnInstruction:", data);
return this.getInstructionExecutionStatusInPolling()
.filter(data => data != "Polling")
.take(1)
.flatMap((data) => {
console.info("flatMap of getInstructionExecutionStatusInPolling:", data);
return this.getInstructionResult();
}).map((data) => {
console.info("Map of getInstructionResult:", data);
return data;
});
});
}
public runAnInstruction(): Observable<string> {
return Observable.of("StartRun");
}
public getInstructionResult(): Observable<string> {
return Observable.of("FinalResult");
}
public getInstructionExecutionStatusInPolling(): Observable<string> {
return Observable.interval(1000)
.concatMap(data => {
return this.getInstructionExecutionStatus();
});
}
public getInstructionExecutionStatus(): Observable<string> {
return Observable.of("Polling", "Terminate");
}
Here plunk:
https://plnkr.co/edit/c1cahMtVARQnLgnHWlEe?p=preview
Main problem is that i just would like to be notify about "evolution" of inner stream outside.
Right now we have "next" event on main only when all inner flatMap are completed.
How to get notify? How can i emit explicit values to main stream for example during polling?
Thanks.
I found a solution to share.
Here plunker updated:
https://plnkr.co/edit/c1cahMtVARQnLgnHWlEe?p=preview
Basically i create a simple observable using : https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/create.md
then i call programmatically next method and complete finally:
public runInstructionAndGetResult(): Observable<string> {
return Observable.create((ops)=> {
ops.next(1);
this.runAnInstruction()
.concatMap((data) => {
ops.next(2);
console.info("flatMap of runAnInstruction:", data);
return this.getInstructionExecutionStatusInPolling()
.filter(data => data != "Polling")
.take(1)
.concatMap((data) => {
ops.next(3);
console.info("flatMap of getInstructionExecutionStatusInPolling:", data);
return this.getInstructionResult();
}).map((data) => {
console.info("Map of getInstructionResult:", data);
ops.next(4);
ops.complete();
return data;
});
}).subscribe();
});
}