Reputation: 117
I am new with RxJS and I want to learn how to write code using it in clean way. I have nested subscription and I' ve tried to rewrite this, but with no result.
firstMethod() {
this.testMethod(name)
console.log(this.currentPerson)
}
testMethod() {
this.myService.search(name).subscribe(response => {
if(result) {
this.currentPerson = result
} else {
this.myService.create(name).subscribe(result => {
const person = {id: result.id, name: name}
this.currentPerson = person
})
}
})
}
Unfortunatelly despite the code is messy, there is something wrong with some piece after 'else' because console.log shows undefined. Any tips how to fix it?
Upvotes: 3
Views: 3716
Reputation: 14740
To effectively handle nested subscribes, you should use one of the "Higher Order Mapping Operator", which do a few nice things for you:
In this case, switchMap
is a good choice because it will only allow a single inner subscription at a time, so whenever myService.search(name)
is called a new inner subscription for myService.create(name)
is created, and the previous one is automatically unsubscribed.
@Rafi Henig's answer shows a good example of what this could look like.
.pipe()
. You can define transformations to your observable output using pipeable operators without actually subscribing.I would suggest you don't subscribe in your testMethod()
, but rather return an observable. Also, let's give "testMethod()" a more meaningful name for further discussion: "getPerson()".
getPerson(name: string): Observable<Person> {
return this.myService.search(name).pipe(
switchMap(result => {
return iif(
() => result,
of(result),
this.myService.create(name).pipe(
map(({ id }) => ({ id, name }))
)
)
}),
tap(person => this.currentPerson = person)
);
}
console.log shows undefined. Any tips how to fix it?
1 firstMethod() {
2 this.getPerson(name)
3 console.log(this.currentPerson)
4 }
The reason for the undefined
is because the code is asynchronous. Line 2 is executed, then line 3 immediately after, but the async work hasn't been finished yet, so this.currentPerson
hasn't been set yet.
since our getPerson()
method now returns an observable, we can subscribe and do your console.log()
inside the subscribe:
1 firstMethod() {
2 this.getPerson(name).subscribe(
3 () => console.log(this.currentPerson)
4 )
5 }
To simplify, we don't even need this.currentPerson
anymore, because the person is emitted through the stream!
1 firstMethod() {
2 this.getPerson(name).subscribe(
3 person => console.log(person)
4 )
5 }
And since you want to...
learn how to write code using it in clean way
I think think cleanest way would probably be to define your "person result" as an observable and ditch this.currentPerson
.
person$ = this.getPerson(name);
So now you have this.person$
which can be subscribed to and will always have the current value of person. No need to "manually" update this.currentPerson
.
Well... almost. We need to consider what happens when the search term changes.
Let's assume the search term "name" is coming from a form control input.
When using Reactive Forms the input value is an observable source, so we can define our person$
from the search term:
searchTerm$ = this.searchInput.valueChanges();
person$ = this.searchTerm$.pipe(
switchMap(searchTerm => this.getPerson(searchTerm))
);
getPerson(name: string): Observable<Person> {
return this.myService.search(name).pipe(
switchMap(result => {
return iif(
() => result,
of(result),
this.myService.create(name).pipe(
map(({ id }) => ({ id, name }))
)
)
})
);
}
Notice we've defined two different observables, but we haven't subscribed yet! Now, we can leverage the async
pipe in our template to handle the subscription, keeping our component code nice and simple.
<p *ngIf="person$ | async as person">
We found {{ person.name }} !
</p>
I know this has gotten a bit long winded, but I hope you see how its possible to transform output using pipeable operators and how you can define one observable from another.
Upvotes: 4
Reputation: 6424
Use switchMap to return a new Observable based of the the value of result
using IIF operator as demonstrated below:
this.myService.search(name)
.pipe(
switchMap(result => {
return iif(
() => result,
of(result),
this.myService.create(name).pipe(map(({ id }) => ({ id, name })))
)
})
)
.subscribe(person => {
})
Or optionally:
this.myService.search(name)
.pipe(
switchMap(result => {
if (result) return of(result);
else this.myService.create(name).pipe(map(({ id }) => ({ id, name })));
})
)
.subscribe(person => {
})
Upvotes: 2