Passing params to mapped observable - javascript

I'm fairly new to Observables, Promises, Angular 2, and Javascript.
My question is how do I get a reference to the "item" object here:
getItemTransactions (item: Item): Observable<any> {
// Do some stuff ...
return this.http.post(this.url, body, options)
.map(this.extractData)
.catch(this.handleError);
}
In the mapped extractData helper?
private extractData(res: Response) {
let json = res.json().body
/// How do I assign back to item object here?
item.some_property = json["some_property"]
}
Code come from here:
https://angular.io/docs/ts/latest/guide/server-communication.html#!#extract-data

Map should be used to convert a type to another type. When using the http service you should convert your expected json result to some expected and known type. This can be done using .json() method on the response.
Use subscribe to then do something with the expected result from .json().
All of your actions can be done in the expression body, you do not need separate methods for them. This is a mater of preference so whatever you choose is fine.
See code below.
getItemTransactions (item: Item): Observable<any> {
// Do some stuff ...
return this.http.post(this.url, body, options)
.map((res) => res.json()) // get the data from the json result. This is equivalent to writing {return res.json()}
.subscribe((data) => {
this.doSomethingWithData(data, item); // pass that deserialized json result to something or do something with it in your expression
})
.catch(this.handleError);
}
private doSomethingWithData(data: any, item: Item) {
// Do some stuff ...
item.some_property = data["some_property"];
}

Why would you want to re-assign a method parameter in the first place? You probably want to assign a class property instead (this.item vs item).
But if for some reason you really want to reassign the item param, you could always inline the extractData helper, i.e.:
getItemTransactions(item: Item): Observable<any> {
return this.http.post(this.url, body, options)
.map((res: Response) => {
item = res.json(); // Re-assign some value to `item`
})
.catch(this.handleError);
}
It's probably NOT what you want to do. The usual pattern is to have a function return an observable and subscribe() to that observable somewhere else in your code. You can do the assignment inside the subscribe callback.
This would translate to the following code:
getItemTransactions(item: Item): Observable<any> {
return this.http.post(this.url, body, options)
.map((res: Response) => res.json())
.catch(this.handleError);
}
// Somewhere else in your code
this.getItemTransactions(item: Item).subscribe(data => {
item = data; // for instance
}

Related

Return a Observable from a Subscription with RxJS

I currently have a service that do a HTTP request to an API to fetch data. There are some logic that I want to do to the observable from within the service, but I still want to also subscribe to the Observable in my Component so that I can return any errors to the Component and to the user.
Currently I do:
// service.ts
getData(): Observable<any> {
return this.http.get(url).pipe(catchError(this.handleError)
}
// component.ts
ngOnInit() {
this.service.getData().subscribe(res => {
// Do my logic that belong to the service.ts
// ...
// Do my logic that belongs in the component.ts
// ...
}, err => this.errors = err)
}
What I would like to do is to refactor this so that I handle the logic related to the subscription and the service.ts within the getData() method, and then return an Observable with the HTTP response and any errors to the Component so that I can continue doing things there.
What's the pattern to do this?
I feel like multiple of the patterns and solutions posted is "ugly" or does not follow the Observable pattern (Like doing callbacks).
The cleanest, most "RxJS"-like solution I came up with was to wrap the service method's return value in a second Observable.
So the following:
// service.ts
getData(): Observable<any> {
return new Observable(subscriber => {
this.http.get(url)
.pipe(catchError(this.handleError)
.subscribe(res => {
// Do my service.ts logic.
// ...
subscriber.next(res)
subscriber.complete()
}, err => subscriber.error(err))
})
}
// component.ts
ngOnInit() {
this.service.getData().subscribe(res => {
// Do my component logic.
// ...
}, err => this.errors = err)
}
Use map:
// service.ts:
import { catchError, map } from 'rxjs/operators';
getData(): Observable<any> {
return this.http.get(url).pipe(
map(res => {
/* Your processing here */
return res;
}),
catchError(this.handleError)
)
}
Try this way
service.ts
getData(): Observable<any> {
return this.http.get(url).map(res=> <any>(res['_body']));
}
component.ts
this.service.getData().subscribe(response=>{
var res1 = JSON.stringify(response);
var res2 = JSON.parse(res1);
var res3 = JSON.parse(res2);
}); //parse response based on your response type
Option 1
If you subscribe Observable in component then only component will have that subscription and it must be passed back to service.
Option 2
Use this pattern.
service.ts
getData(doer: Function) {
let subscriptions = Observable.of({ data: 'response', isError: false })// request
.catch(error => Observable.of({ data: error, isError: true })) //handle error
.do(data => doer(data))
.subscribe();
this.handleSubscription(subscriptions); //subscription handling in service
}
component.ts
ngOnInit() {
this.getData(response => {
if (response.isError) {
///
} else {
let data = response.data;
// Process
}
})
}
Be careful: All the answers are for <= Angular 4. In Angular 5, you don't need a map() anymore, so just leave that out. just return this.http.get() as it returns an Observable, where you can subscribe on.
Furthermore, be aware you have to import HttpClient instead of Http.
You can directly use "map" and "catch" function on Observable returned by http.get method.
import { catchError, map } from 'rxjs/operators';
getData(): Observable<any> {
return this.http.get(url)
.map(res => {
/* Your processing here */
return res;
})
.catch(this.handleError);
}
You can remove this, and use map. In subscribe error, you can get error event.
If you use HttpClient, just use get!

Pass result of one RxJS observable into next sequential observable

I am trying to do a basic chaining of RxJS HTTP calls where the first call is going to pass the ID of an object that was created server-side into the second call. What I am observing with my implementation is that only the first call is made and the second chained call is never executed.
Below is roughly the code that I have.
First call:
createItem(itemData) {
return this.http.post(createUrl, itemData)
.map((res) => res.json())
.catch(this.handleError);
}
Second call:
addFileToItem(id, file): Observable<any> {
return this.http.post(secondUrl + id, file)
.map((res) => log(res))
.catch(this.handleError);
}
Function defined mapping one call into the other:
createAndUpload(itemData, file): Observable<any> {
return createItem(itemData, file)
.map((res) => {
if (file) {
return addFileToItem(res.id, file);
}
});
}
And finally, where the observable is executed through a subscribe():
someFunction(itemData, file) {
createAndUpload(itemData, file)
.subscribe(res => {
log(res);
//go do something else
};
}
The problem is in createAndUpload where map just turns the result from the first call into an Observable but you never subscribe to it.
So simply said you just need to use mergeMap (or concatMap in this case it doesn't matter) instead of map. The mergeMap operator will subscribe to the inner Observable and emit its result:
createAndUpload(itemData, file): Observable<any> {
return createItem(itemData, file)
.mergeMap((res) => {
if (file) {
return addFileToItem(res.id, file);
}
return Observable.empty(); // you can't return `undefined` from the inner Observable
});
}
Apparently the map() function doesn't actually return anything, meaning that an Observable object is not returned. Instead, a transforming operator such as mergeMap is needed.
Here is the code that ended up working, also using the "newer" .pipe() operator. The only place the code needed to be changed was in the Observable function where I defined the combination of the two separate Observables. In order to access the mergeMap operator, don't forget to import it from rxjs/operators.
For a reason that I will eventually figure out, I couldn't access the mergeMap without the pipe...
createAndUpload(itemData, file): Observable<any> {
return createItem(itemData, file)
.pipe(
mergeMap((res) => {
if (file) {
return addFileToItem(res.id, file);
}
})
)
}

How to combine the results of two observable in angular?

How to combine the results of two observable in angular?
this.http.get(url1)
.map((res: Response) => res.json())
.subscribe((data1: any) => {
this.data1 = data1;
});
this.http.get(url2)
.map((res: Response) => res.json())
.subscribe((data2: any) => {
this.data2 = data2;
});
toDisplay(){
// logic about combining this.data1 and this.data2;
}
The above is wrong, because we couldn't get data1 and data2 immediately.
this.http.get(url1)
.map((res: Response) => res.json())
.subscribe((data1: any) => {
this.http.get(url2)
.map((res: Response) => res.json())
.subscribe((data2: any) => {
this.data2 = data2;
// logic about combining this.data1 and this.data2
// and set to this.data;
this.toDisplay();
});
});
toDisplay(){
// display data
// this.data;
}
I can combine the results in the subscribe method of the second observable.
But I'm not sure if it's a good practice to achieve my requirement.
Update:
Another way I found is using forkJoin to combine the results and return a new observable.
let o1: Observable<any> = this.http.get(url1)
.map((res: Response) => res.json())
let o2: Observable<any> = this.http.get(url2)
.map((res: Response) => res.json());
Observable.forkJoin(o1, o2)
.subscribe(val => { // [data1, data2]
// logic about combining data1 and data2;
toDisplay(); // display data
});
toDisplay(){
//
}
A great way to do this is to use the rxjs forkjoin operator (which is included with Angular btw), this keeps you away from nested async function hell where you have to nest function after function using the callbacks.
Here's a great tutorial on how to use forkjoin (and more):
https://coryrylan.com/blog/angular-multiple-http-requests-with-rxjs
In the example you make two http requests and then in the subscribe fat arrow function the response is an array of the results that you can then bring together as you see fit:
let character = this.http.get('https://swapi.co/api/people/1').map(res => res.json());
let characterHomeworld = this.http.get('http://swapi.co/api/planets/1').map(res => res.json());
Observable.forkJoin([character, characterHomeworld]).subscribe(results => {
// results[0] is our character
// results[1] is our character homeworld
results[0].homeworld = results[1];
this.loadedCharacter = results[0];
});
The first element in the array always corresponds to the first http request you pass in, and so on. I used this successfully a few days ago with four simultaneous requests and it worked perfectly.
TRY with forkJoin if it's not working then give this a try combineLatest()
What it do - it combine the last emitted value from your stream array into one before completion of your stream array.
Observable.combineLatest(
this.filesServiceOberserval,
this.filesServiceOberserval,
this.processesServiceOberserval,
).subscribe(
data => {
this.inputs = data[0];
this.outputs = data[1];
this.processes = data[2];
},
err => console.error(err)
);
We can combine observables in different ways based on our need. I had two problems:
The response of first is the input for the second one: flatMap() is
suitable in this case.
Both must finish before proceeding further: forkJoin()/megre()/concat() can be used depending on how you want
your output.
You can find details of all the above functions here.
You can find even more operations that can be performed to combine observables here.
You can merge multiple observables into a single observable and then reduce the values from the source observable into a single value.
const cats = this.http.get<Pet[]>('https://example.com/cats.json');
const dogs = this.http.get<Pet[]>('https://example.com/dogs.json');
const catsAndDogs = merge(cats, dogs).pipe(reduce((a, b) => a.concat(b)));
You can also use mergemap which merges two obserables
Merge map documentation

Custom mapping function for observable

I have an angular service that I want to return an object I have defined in an interface.
The tutorial I am following assumes that the response is identical to the interface but in my scenario I want to process the object a bit in order to just return some of the data.
My service method based on the tutorial is:
getResults(): Observable<IPageSpeedResult> {
return this._http.get(this._pageSpeedApiUrl)
.map((response: Response) => <IPageSpeedResult> response.json())
.do(data => console.log('All: ' + JSON.stringify(data)))
.catch(this.handleError);
}
How do I extend the map method to include my logic for building the IPageSpeedResult object from the returns data that I want? I basically need to access an array and then iterate over it to populate the return object.
Well, you can do all of that inside the map operator.
getResults(): Observable<IPageSpeedResult> {
return this._http.get(this._pageSpeedApiUrl)
.map((response: Response) => <IPageSpeedResult> response.json())
.map(data => this.transformData(data))
.do(data => console.log('All: ' + JSON.stringify(data)))
.catch(this.handleError);
}
transformData(data: any) : IPageSpeedResult {
// do your magic here
}
You can add your logic to either service method or when you subscribe the data, based on your business logics
getResults(): Observable<IPageSpeedResult> {
return this._http.get(this._pageSpeedApiUrl)
.map((response: Response) => <IPageSpeedResult> response.json())
.do((data) => {
// your custom logic with the data
})
.catch(this.handleError);
}
When subscribing to data
this.serviceObject.getResults()
.subscribe((data)=>{
// your custom logic with the data
})
Note: It is not good practice to have the business logic in the services as they can be reused and construct a different set of data(some other custom mapping)

How can I return the Http response?

I'm wondering if following thing is possible to do:
I need to return the response from http GET request directly instead of returning Observable<Response> instance.
The example might clarify whole thing a bit:
#Injectable()
export class ExampleService {
constructor( #Inject(Http) protected http: Http) { }
static model: { [uri: string]: any } = {}
public get(uri: string): any {
if (typeof ExampleService.model[uri] === 'undefined') {
ExampleService.model[uri] = this.http.get(uri).map(response => response.json()) // additionally do some magic here, it is Observable<any> instance now
}
return ExampleService.model[uri]
}
}
Summary: according to Günter Zöchbauer answer above solution is not possible, instead of that I need to use something like this:
public get(uri: string): Observable<any> {
return new Observable(observer => {
if (!ExampleService.model[uri]) {
let sub = this.http.get(uri).map(response => response.json()).subscribe(
src => observer.next(ExampleService.model[uri] = src),
err => console.error(err),
() => observer.complete()
)
return () => sub.unsubscribe()
}
observer.next(ExampleService.model[uri])
observer.complete()
})
}
This is not possible because the HTTP request is async and the get() method returns before the call to the server is even made. Instead when the response from the server arrives the callback passed to subscribe(...) is called.
There is no way to go back from async to sync execution.
You can only return the observable for the caller to subscribe to it and do something when the response arrives.

Categories

Resources