If you want to efficiently handle nested subscribes, employing one of the "Higher Order Mapping Operator" is recommended as they offer several advantages:
- They map the incoming value to another observable.
- They subscribe to it, ensuring that its values are emitted into the stream.
- They manage unsubscribing from these "inner subscriptions" automatically.
For this scenario, switchMap
is a good option because it permits only one inner subscription at a time. This means that whenever myService.search(name)
is invoked, a new inner subscription for myService.create(name)
is activated, with the previous one being automatically unsubscribed.
@Rafi Henig's response provides a clear example of how to implement this solution effectively.
- Note the usage of
.pipe()
. You can apply transformations to your observable output using pipeable operators without actually subscribing.
I recommend avoiding subscribing within your testMethod()
, and instead returning an observable. Additionally, consider giving 'testMethod()' a more descriptive name like 'getPerson()' for better clarity in future discussions:
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)
);
}
The console.log displays undefined. Any suggestions on how to resolve this?
1 firstMethod() {
2 this.getPerson(name)
3 console.log(this.currentPerson)
4 }
The reason for the 'undefined' output is due to the asynchronous nature of the code. Line 2 is executed, followed immediately by line 3. However, since the async operation hasn't completed yet, 'this.currentPerson' remains unset.
Since our 'getPerson()' method now returns an observable, we can subscribe and utilize your 'console.log()' action inside the subscription:
1 firstMethod() {
2 this.getPerson(name).subscribe(
3 () => console.log(this.currentPerson)
4 )
5 }
To simplify further, we can eliminate the need for 'this.currentPerson' altogether, as the person is emitted via the stream:
1 firstMethod() {
2 this.getPerson(name).subscribe(
3 person => console.log(person)
4 )
5 }
Considering your desire to...
Learn how to write clean code utilizing it effectively
A neat approach would involve defining your 'person result' as an observable and discarding 'this.currentPerson':
person$ = this.getPerson(name);
Thus, you now have 'this.person$', which can be subscribed to and always holds the most up-to-date person data. Manually updating 'this.currentPerson' becomes unnecessary.
Almost there... Just remember to account for changes in the search term.
Assuming the search term 'name' originates from a form control input:
When utilizing Reactive Forms, the input value serves as an observable source, allowing us to link our 'person$' to 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 }))
)
)
})
);
}
We've defined two distinct observables, but haven't subscribed yet! Now, we can make use of the async
pipe in our template to manage the subscription, maintaining simplicity in our component code.
<p *ngIf="person$ | async as person">
We found {{ person.name }} !
</p>
This might be lengthy, but I hope it illustrates how to transform outputs using pipeable operators and establish one observable based on another.