I want to apply an asynchronous transformation function on the value emitted by an observable.
#Injectable
export class ApiService{
constructor(private http: HttpClient){}
getSomething(url): Observable<any>{
return this.http.get(url);
}
}
In the code above, I want to apply a transformation function myFunc, which returns a promise, on the value emitted by this.http.get(url).
Normally I would use the map operator of RxJS, but since the transformation function returns a promise, I could not find a way to handle it.
For example, let my function be:
function myFunc(value){
return new Promise((resolve, reject) => {
// modify the value async
resolve(modifiedValue);
// ...
});
}
Is there an appropriate way to handle this task? I think the following is not suitable, am I right?
return this.http.get(url).map(myFunc);
Any help will be much appreciated.
Note: I'm using RxJS 5.5.2
Use the mergeMap operator to take in the response value, perform some modification asynchronously via another Observable operation, and then return the modified value. This operator will merge the values emitted by HttpClient and your modifier Observable and return a single Observable that emits the mutated values.
EDIT: Including the Observable.fromPromise bit from #bygrace's comment for a more complete answer.
i.e.
EDIT: For RxJs v5.5+
import { pipe } from 'rxjs/util/pipe';
import { mergeMap } from 'rxjs/operators';
#Injectable
export class ApiService{
constructor(private http: HttpClient){}
getSomething(url): Observable<any>{
return this.http.get(url).pipe(
mergeMap(myFunc)
);
}
private myFunc(x): Observable<any> {
// do some asynchronous modification that returns an Observable
return Observable.fromPromise(x);
}
}
Pre RxJs v5.5
#Injectable
export class ApiService{
constructor(private http: HttpClient){}
getSomething(url): Observable<any>{
return this.http.get(url)
.mergeMap(data => {
// do some asynchronous modification that returns an Observable
return Observable.fromPromise(data);
});
}
}
See: http://reactivex.io/rxjs/class/es6/Observable.js~Observable.html#instance-method-mergeMap
Related
The component has variable applicationObs as observer:
export class OrderDetailsComponent implements OnInit {
public applicationObs: Observable<ApplicationResponse>;
public getSections(): SidebarMenuItem[] {
const fabricApplicationSidebar = fabricApplicationSidebarMenu(this.application); // Here
}
}
How to pass applicationObs to fabricApplicationSidebarMenu() when this.application is not observer, but ApplicationResponse.
How to pass async data as variable in function?
I'd suggest you think about this a little differently, what you actually want to do is add a new function onto your observable pipe. so I'd do something like this:
export class OrderDetailsComponent implements OnInit {
public applicationObs: Observable<ApplicationResponse>;
public getSections(): Observable<SidebarMenuItem[]> {
return applicationObs.pipe(
map(m => fabricApplicationSidebarMenu(m))
);
}
}
Here were adding a new pipe onto the existing observable and returning that. this is still async so in itself doesn't do anything. To get the sections you'll still need to subscribe to the resulting observable:
this.getSections().subscribe((s: SidebarMenuItem[]) => {
});
You could also potentially use an async pipe if you wished. See here
Note that the above will trigger the applicationObs and whatever fabricApplicationSidebarMenu does on subscribe. This may or may not be desirable depending on what your planning on doing and what these functions do.
what do you want to do?
something like this?
this.applicationObs.subscribe(
(applicationResponse: ApplicationResponse) => {
const fabricApplicationSidebar = fabricApplicationSidebarMenu(applicationResponse); // Here
});
Imagine we have the following factory:
#Injectable()
export class DataClassFactory {
constructor(
private dataService: DataService,
) { }
public createThing(initialData?: InitialData): AsyncSubject<DataClass> {
let dataClass: AsyncSubject<DataClass> = new AsyncSubject<DataClass>();
if (!!initialData) {
dataClass.next(new DataClass(initialData));
dataClass.complete();
} else {
this.dataService.getData().subscribe((dataResponse) => {
dataClass.next(new ReportRequest(dataResponse));
dataClass.complete();
});
}
}
return dataClass;
}
}
We inject this factory, invoke the createThing method, and subscribe to the response in some component. I originally tried to use a plain Subject, but then I realized that in the case where we already have initial data, next() is called before the response is returned, so the subscriber in the component never gets that value.
My question is: is this correct situation in which to use an AsyncSubject, or is there a different/better way to handle this sort of method that has potential synchronous and asynchronous timelines?
I would do something along these lines
public createThing(initialData?: InitialData): Observable<DataClass | ReportRequest> {
if (!!initialData) {
const data = new DataClass(initialData);
return of(data);
} else {
return this.dataService.getData()
.pipe(map(dataResponse => new ReportRequest(dataResponse));
}
}
Whoever calls createThing would get an Observable to which it would have to subscribe.
This Observable would emit an instance of DataClass if initialData is not null, otherwise it would return and instance of ReportRequest as soon as dataService responds.
I am having trouble to return an observable. It seems like the codes inside the mergeMap is not running at all.
Codes:
book.service.ts
import {HttpClient, HttpHeaders} from '#angular/common/http';
export class bookService {
constructor(
private http: HttpClient,
...others
) {}
addNewBook(book): Observable<Book>{
##### Tried to use mergeMap otherwise the return type won't match
return this.tokenService.getToken().mergeMap((token: string) => {
console.log("Fire this...") <===== This is never output.
const myUrl = "www.testurl.com";
const parameters = {
bookTitle: book.name,
};
return this.http.post<Book>(myUrl, book);
})
}
token.service.ts
public token$: Subject<string>;
..others
public getToken(): Observable<string> {
return this.token$; <= return Observable<string> not Observable<Book>
}
book.component.ts that calls the addNewBook method.
...others
return Promise.resolve()
.then(() => {
return bookService.addNewBook(book);
}).then((result) => {
console.log(result);
})
I can't really change the token service because it's used on other place, I am not sure why the codes inside the mergeMap is not running. Can someone help me about it? Thanks a lot!
It won't work unless you subscribe to the results of bookService.addNewBook(book). Just returning it from the then callback won't subscribe. You need to at least add toPromise.
...others
return Promise.resolve()
.then(() => {
return bookService.addNewBook(book).toPromise();
}).then((result) => {
console.log(result);
})
In order for the mergeMap() to be be triggered, the token$ subject inside token.service.ts needs to emit a value (via .next()) after addNewBook() is subscribed to by a consumer.
One of the things to keep in mind with Subjects is that 'late subscribers' won't receive a value off of them until the next time .next([value]) is called on that Subject. If each subscriber, no matter how late, needs to immediately receive the last value generated by that source (Subject) then you can use BehaviorSubject instead.
From your short code example it is hard to see where the Observable generated by addNewBook() is being subscribed to though. Remember, a Observable won't execute until it has a subscriber.
I am working on an Angular4 app.
Here is a service I am using to get the data-
export class BookingService {
constructor(private http: HttpClient) {}
getMemberBookings(memberID: number): Observable<any> {
return this.http.get('http://myapi.com/bookings/member/'+memberID).map(response => response['bookings']);
}
}
And then in my component-
export class PrintButtonComponent implements OnInit {
bookings: any;
constructor(private service: BookingService) {}
ngOnInit() {}
downloadPDF() {
this.getBookings(memberID);
//pdf creation logic
}
getBookings(memberID: number) {
this.service.getMemberBookings(memberID).subscribe(data => this.bookings = data);
}
}
The problem is I want to use the data from the service in the downloadPDF method as there is other data in it that will be needed to create the PDF.
But when I return the data from the subscribe or set it to a property, it is giving undefined. I understand that this is due to asynchronous nature, but I dont want to put my pdf creation logic inside the subscribe method.
So how do I solve this problem? I am quite new to Angular and observables.
Thank you.
Since the code above doesn't involve multiple values per observable and doesn't require to stick to them, this can be done with async..await with no extra nesting:
async downloadPDF() {
await this.getBookings(memberID);
//pdf creation logic
}
async getBookings(memberID: number) {
this.bookings = await this.service.getMemberBookings(memberID).toPromise();
}
As any other RxJS operator, toPromise has to be imported.
I am trying to turn the observable for params that I get off of the ActivatedRoute into a promise but haven't had any luck. I have been able to turn http requests into promises successfully:
this.riaService.getAdvisorsForState(this.activeState)
.then(rias => {
this.riasForState = rias.json();
console.log(this.riasForState);
});
// this all works ^
But I have not been able to turn the 'activatedRoute.params' into a promise:
export class InvestmentAdvisorStateComponent implements OnInit {
constructor(private activatedRoute: ActivatedRoute, private riaService: InvestmentAdvisorService) { }
getStateFromUrl() {
this.activatedRoute.params
.toPromise()
.then(params => {
console.log('HERE',params)
});
}
// this does not work for me ^
This is what I have right now:
getStateFromUrl() {
this.activatedRoute.params
.subscribe((param: any) => {
this.activeState = param['state'];
});
}
// works ^
I am hoping to implement this as a promise thought so I can .then off of it. Any help on how to do this is greatly appreciated.
The main difference between observable and promise is, that observable can emit multiple events while a promise only emits a single event.
To make your example work, I assume you are only interested in the first event.
getStateFromUrl() {
this.activatedRoute.params
.first()
.toPromise()
.then(params => {
console.log('HERE',params)
});
}
This way the promise created with toPromise() is completed with the first params event.