1. Concat
The concat operator in RxJS is used to concatenate multiple Observables together, one after the other.
Working
- Subscribes to the first Observable and waits for it to complete.
- Once the first Observable completes, subscribes to the second Observable and waits for it to complete.
- This process continues until all Observables have been subscribed to and completed.
Example:
Lets suppose we have a service, where we will make http call but for now we are simply sending mock data from there using RXJS of operator.
serivce.ts file
import { Injectable } from '@angular/core';
import { of } from 'rxjs';
@Injectable({
providedIn: 'root',
})
export class ApiService {
// creating mock data sets
public data1 = {
name: 'John Doe',
age: 30,
};
public data2 = {
name: 'John Doe2',
age: 30,
};
public data3 = {
name: 'John Doe3',
age: 30,
};
constructor() {}
// function with respect to each data, these functionc can make http call instead of sending local data
public getUser1Data() {
return of(this.data1); // or return this.http.get(...)
}
public getUser2Data() {
return of(this.data2);
}
public getUser3Data() {
return of(this.data3);
}
}
Component.ts file
import { Component, OnDestroy } from '@angular/core';
import {
takeUntil
} from 'rxjs/operators';
import {
Subject,
concat
} from 'rxjs';
import { ApiService } from './app.service';
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent implements OnDestroy {
private unsubscribe: Subject<void> = new Subject<void>();
public httpRequests$;
constructor(private httpService: ApiService) {
// creating array of http request that we need to make
this.httpRequests$ = [
this.httpService.getUser1Data(),
this.httpService.getUser2Data(),
this.httpService.getUser3Data(),
];
}
ngOnInit() {
concat(...this.httpRequests$).pipe(takeUntil(this.unsubscribe)).subscribe((response) => {
console.log('Concatenated Response:', response);
});
//------ OR --------
concat(...this.httpRequests$).pipe(takeUntil(this.unsubscribe)).subscribe({
next: (response) => {
console.log('Concatenated Response:', response);
},
error: (error) => {
console.error('Error occurred:', error);
},
complete: () => {
console.log('All requests completed');
},
});
}
ngOnDestroy() {
this.unsubscribe.next();
this.unsubscribe.complete();
}
}
Output
Use cases:
- Sequential execution: When you need to execute multiple Observables in a specific order, one after the other.
- Dependent Observables: When the next Observable depends on the result of the previous one.
2. Forkjoin
The forkJoin operator in RxJS is used to combine multiple Observables together and wait for all of them to complete.
Working
- Subscribes to all Observables simultaneously.
- Waits for all Observables to complete.
- Emits an array of values, where each value corresponds to the last value emitted by each Observable.
** Consider previous service.ts file only and make below changes in component.ts file.
Component.ts
import { Component, OnDestroy } from '@angular/core';
import {
takeUntil
} from 'rxjs/operators';
import {
Subject,
forkJoin
} from 'rxjs';
import { ApiService } from './app.service';
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent implements OnDestroy {
private unsubscribe: Subject<void> = new Subject<void>();
public httpRequests$;
constructor(private httpService: ApiService) {
this.httpRequests$ = [
this.httpService.getUser1Data(),
this.httpService.getUser2Data(),
this.httpService.getUser3Data(),
];
}
ngOnInit() {
forkJoin(this.httpRequests$).pipe(takeUntil(this.unsubscribe)).subscribe({
// here we need to pass array as a param inside forkJoin instead of spreading array with (...) spread operator.
next: (response) => {
console.log('Response:', response);
},
error: (error) => {
console.error('Error occurred:', error);
},
complete: () => {
console.log('All requests completed');
},
});
}
ngOnDestroy() {
this.unsubscribe.next();
this.unsubscribe.complete();
}
}
Output:
Use cases:
- Parallel execution: When you need to execute multiple Observables in parallel and wait for all of them to complete.
- Combining results: When you need to combine the results of multiple Observables into a single array.
3. MergeMap
The mergeMap operator in RxJS is used to map each value emitted by an Observable to another Observable, and then merge the emissions from all the resulting Observables.
How it works:
- Maps each value emitted by the source Observable to another Observable.
- Subscribes to all the resulting Observables and merges their emissions.
- Emits the values from all the Observables as they arrive.
import { Component, OnDestroy } from '@angular/core';
import {
mergeMap,
takeUntil,
toArray
} from 'rxjs/operators';
import {
from,
Subject
} from 'rxjs';
import { HttpClient } from '@angular/common/http';
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent implements OnDestroy {
private unsubscribe: Subject<void> = new Subject<void>();
constructor(private http: HttpClient) {}
ngOnInit(): void {
const productIds = [1, 2, 3, 4, 5];
from(productIds)
.pipe(
mergeMap((id) => {
return this.http.get(`https://api.example.com/products/${id}`);
}),
toArray()
)
.subscribe({
next: (products) => console.log('Products:', products),
error: (error) => console.error('Error:', error),
});
}
ngOnDestroy() {
this.unsubscribe.next();
this.unsubscribe.complete();
}
}
Output:
The output of this code will be an array of product details, where each product detail is an object containing the details of a product.
Here's an example of what the output might look like:
[
{
"id": 1,
"name": "Product 1",
"description": "This is product 1",
"price": 10.99
},
{
"id": 2,
"name": "Product 2",
"description": "This is product 2",
"price": 9.99
},
{
"id": 3,
"name": "Product 3",
"description": "This is product 3",
"price": 12.99
}
...
]
The actual output will depend on the API endpoint and the data it returns.
Order of products:
Note that the order of the products in the output array may not necessarily match the order of the product IDs in the original array [1, 2, 3, 4, 5]
. This is because the HTTP requests are made concurrently, and the responses may arrive in a different order.
If you need to preserve the order of the products, you can use a different approach, such as using **concatMap**
instead of **mergeMap**
. **concatMap**
will make the HTTP requests sequentially, preserving the order of the products in the output array.
// Merge map cannot be used for search functionality to search on each keypress, because it will not cancel previous request and hence we will have lots of response for this we use switchMap.
4. SwitchMap
Like mergeMap but when the source Observable emits cancel any previous subscriptions of the inner Observable.
Here's an example of using switchMap
in Angular:
Let's say we have a search input field, and we want to fetch search results from an API as the user types:
import { Component, OnInit } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { FormControl } from '@angular/forms';
import { debounceTime, distinctUntilChanged, switchMap } from 'rxjs/operators';
@Component({
selector: 'app-example',
template: `
<input [formControl]="searchControl" type="text" placeholder="Search...">
<ul>
<li *ngFor="let result of results">{{ result }}</li>
</ul>
`,
})
export class ExampleComponent implements OnInit {
searchControl = new FormControl();
results = [];
constructor(private http: HttpClient) {}
ngOnInit(): void {
this.searchControl.valueChanges
.pipe(
debounceTime(500),
distinctUntilChanged(),
switchMap((query) => {
return this.http.get('https://api.example.com/search?q=${query}');
})
)
.subscribe({
next: (response: any) => {
this.results = response.results;
},
error: (error) => console.error('Error:', error),
});
}
}
Explanation:
In this example:
- We create a
FormControl
for the search input field. - We use
valueChanges
to get an Observable that emits the value of the input field whenever it changes. - We use
debounceTime
to delay the emission of values by 500ms, so that we don't make too many requests as the user types. - We use
distinctUntilChanged
to only emit values that are different from the previous value. - We use
switchMap
to map the input value to an HTTP request that fetches search results from the API. - We subscribe to the resulting Observable and update the
results
array with the search results.
The **switchMap**
operator will cancel any pending HTTP requests and switch to the new request whenever the input value changes.
Benefits:
-
switchMap
allows us to cancel previous requests and only handle the latest result, which can improve performance and reduce unnecessary requests. - It ensures that the search results are always up-to-date with the latest input value.
When to use switchMap
:
- When you need to cancel previous requests and only handle the latest result.
- When you want to ensure that the results are always up-to-date with the latest input value.
5. CombineLatest
The combineLatest operator in RxJS is used to combine the latest values from multiple Observables
How it works:
- Subscribes to all Observables.
- When any of the Observables emit a new value, combines the latest values from all Observables and emits the result.
Example
service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { combineLatest, Observable } from 'rxjs';
import { map } from 'rxjs/operators';
@Injectable({
providedIn: 'root',
})
export class DataService {
constructor(private http: HttpClient) {}
getUserData(userId: number): Observable<any> {
return this.http.get(`https://api.example.com/users/${userId}`);
}
getUserPosts(userId: number): Observable<any> {
return this.http.get(`https://api.example.com/users/${userId}/posts`);
}
getUserDataAndPosts(userId: number): Observable<any> {
return combineLatest([
this.getUserData(userId),
this.getUserPosts(userId),
]).pipe(
map(([userData, userPosts]) => ({ userData, userPosts }))
);
}
}
Component.ts
import { Component, OnInit } from '@angular/core';
import { DataService } from './data.service';
@Component({
selector: 'app-example',
template: `
<p>User Data: {{ userData | json }}</p>
<p>User Posts: {{ userPosts | json }}</p>
`,
})
export class ExampleComponent implements OnInit {
userData: any;
userPosts: any;
constructor(private dataService: DataService) {}
ngOnInit(): void {
this.dataService.getUserDataAndPosts(1).subscribe((result) => {
this.userData = result.userData;
this.userPosts = result.userPosts;
});
}
}
Example 2
@Component({
selector: 'app-example',
template: `
<input [formControl]="nameControl" placeholder="Name">
<input [formControl]="ageControl" placeholder="Age">
<p>Combined value: {{ combinedValue }}</p>
`,
})
export class ExampleComponent implements OnInit, OnDestroy {
nameControl = new FormControl('');
ageControl = new FormControl('');
combinedValue = '';
private destroy$ = new Subject<void>();
ngOnInit(): void {
combineLatest([
this.nameControl.valueChanges,
this.ageControl.valueChanges,
]).pipe(
map(([name, age]) => ({ name, age })),
takeUntil(this.destroy$)
).subscribe((combinedValue) => {
this.combinedValue = `Name: ${combinedValue.name}, Age: ${combinedValue.age}`;
});
}
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
}
How it works:
- We create two FormControl instances for name and age.
- We use combineLatest to combine the value changes of both controls.
- We use map to transform the combined values into an object.
- We use takeUntil to unsubscribe when the component is destroyed.
- We subscribe to the combined Observable and update the combinedValue property.
Benefits:
- Parallel requests: combineLatest allows both HTTP requests to be made in parallel.
- Simplified data handling: The combined results are transformed into a single object, making it easier to handle the data in the component.
This operator is very useful when you need for example to deal with filters in your app.
Top comments (0)