Angular RxJS: examples cookbook
- share a variable between multiple components and services
- filter a list using observables
- search the details when an observable is emitted - call an observable after a value is emitted
- retrieve related data when a value is selected/emitted
- manage a list / cart using observables
This is a collection of recipes to use declarative methods to work with data using Angular, Typescript and RxJS.
The list of examples will be improved in the future and will try to cover most of the use cases for a 'standard' application.
Create a variable in a service that can be used by multiple components
The goal is to have a variable (selectedStudent
) that is stored in a service and it's accessible to other components and services of the application.
export class ExampleService {
// a private variable that contains the value
private selectedStudent = new BehaviorSubject<string>('');
// a public method that exposes the variable as observable
public readonly selectedStudent$ : Observable<string> = this.selectedStudent.asObservable();
// a public method that allows other components to update the local variable
public setSelectedStudent(student: string): void {
this.selectedStudent.next(student);
}}
Filter a list using observables
Our filter is an observable and the list to filter is an observable too.
studentsFound$ = combineLatest([
this.allStudents$,
this.nameToSearch$
]).pipe(
map(([students, selectedName]) =>
students.filter(student =>
selectedName ? student.name.includes(selectedName) : true )),
catchError(this.handleError)
);
Because we are working with 2 streams we have to combine them. When we received the 2 streams we can start to search the name of the student in the list of students.
The combineLatest will return an observable with the list of students found.
Search the details when an observable is emitted
The user choose a student from the list and we want lo search the details from the backend.
This case is particularly interesting because we are using a parameter (student.id
) in a declarative method to load the data.
To use a parameter we have to emit it into the observable (in this case using selectedStudent$
.
studentDetails$ = this.selectedStudent$.pipe(
switchMap(student =>
this.http.get<StudentDetail>(`${url-backend-service}/${student.id}`)))
This method load the details from the backend every time a new observable (selectedStudent) is emitted.
You can use this method to load the details from the backend or some linked data when a new record is selected.
Retrieve related data when a value is selected / emitted
If we want to load related data we can use an higher-order RxJS operator (SwitchMap), this will subscribe and unsubscribe the inner observable.
The forkJoin
will retrieve all the results of the multiple get
as an array
.
studentLessonsDetails$ = this.selectedStudent$.pipe(
switchMap(student =>
forkJoin(student.lessons.map(lesson => this.http.get<Lesson>(`${url-backend-service}/lesson/${lesson.id}`)))
))
Manage a list / cart using observables
In our case we have a cart that can contains different objects types in given quantities.
The objects can be added and removed to/from the cart using actions.
private itemSubject = new Subject<Action<ListItem>>();
itemAction$ = this.itemSubject.asObservable();
addToList(book: Book): void {
this.itemSubject.next({
item: {book, 1},
action: 'add'
});
}
cartItems$ = this.itemAction$.pipe(
// scan maintains the array of cart items
scan((items, itemAction) => this.updateCart(items, itemAction), [] as CartItem[])
)
private updateCart(items: CartItem[], option: Action[CartItem]): CartItem[] {
if('add' === option.action) {
return [...items, option.item]
} else if ('delete' === option.action) {
return item.filter(...)
}
return [...items];
}
To maintain the array of data we can use the scan
RxJS operator.