C is for combineLatest
Julian Dierkes
Posted on August 9, 2020
Let's check out the first function in this series: combineLatest.
Use this function whenever you are interested in the latest output of two or more Observables.
One can understand the behavior pretty good when looking at the marble diagram from the official documentation.
The upper observables emit the values in the marbles and are combined with combineLatest. As you can see it emits tha latest values of all its Observables whenever on of them emits. These values are then returned as array.
Enough theory! Show us the example!
Ok. I first used this operator in the following scenario:
We had a relational database with a many-to-many relationship and two different models that had to be consolidated and displayed in the UI written in Angular. Lets use Course
and Student
as examples here:
export interface Course {
id: string;
name: string;
studentIds: string[];
}
export interface Student {
id: string;
name: string;
}
However the model we want to display should look like this:
interface CourseWithStudents {
id: string;
name: string;
students: Student[];
}
So we need to merge both models into one view model.
Lets have a look on how to achieve this with the combineLatest
operator.
We have two Angular services providing the data in form of an Observable, the CourseService
and the StudentService
.
I'm also simulating some http delay in the services using setTimeout()
.
export class CourseService {
private _courses: Course[] = [
{id: '1', name: 'German', studentIds: ['1', '3', '4']},
{id: '2', name: 'Math', studentIds: ['2', '3', '5']},
{id: '3', name: 'Biology', studentIds: ['1', '2']}
];
courses: BehaviorSubject<Course[]> = new BehaviorSubject<Course[]>([]);
constructor() {
setTimeout(() => {
this.courses.next(this._courses);
}, 1000);
}
}
export class StudentService {
private _students: Student[] = [
{id: '1', name: 'Johnny Rakete'},
{id: '2', name: 'Melissa Woods'},
{id: '3', name: 'Gordon Thorner'},
{id: '4', name: 'Jamy Dormer'},
{id: '5', name: 'Will Higgs'},
{id: '6', name: 'Smantha Claire Restful'},
];
students: BehaviorSubject<Student[]> = new BehaviorSubject<Student[]>([]);
constructor() {
setTimeout(() => {
this.students.next(this._students);
}, 2000);
}
}
This is the code that combines both Observable results once any of them emits:
this.coursesWithStudents = combineLatest([this.courseService.courses, this.studentService.students])
.pipe(
tap(latestResults => {
console.log('combineLatest emitted', latestResults);
}),
map(latestResults => {
const [latestCourses, latestStudents] = latestResults;
return latestCourses.map(course => ({
id: course.id,
name: course.name,
students: latestStudents.filter(student => course.studentIds.includes(student.id))
}));
}));
This is basically just the combineLatest
operator with some mapping logic attached. The whole code returns an Observable which we can consume e.g. with the async
pipe directly in the template.
Lets see the output of the above code in three tables, one for courses, one for students and one for the combined result.
I've also integrated some console logging which pops up after the service timeouts emit. You can see that our merged Observable emits three times.
- When the initial empty arrays are emitted by the BehaviorSubjects
- When the courses array is emitted
- When the students array is emitted
So we achieved what was desired. Every time the courses or students are updated the resulting table gets a fresh merged view model.
Thanks for reading! Feel free to check out the other articles of this series and stay tuned for the next ones.
Posted on August 9, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.