C is for combineLatest

juliandierkes

Julian Dierkes

Posted on August 9, 2020

C is for combineLatest

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.
Marble diagram of combineLatest
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[];
}
Enter fullscreen mode Exit fullscreen mode
export interface Student {
  id: string;
  name: string;
}
Enter fullscreen mode Exit fullscreen mode

However the model we want to display should look like this:

interface CourseWithStudents {
  id: string;
  name: string;
  students: Student[];
}
Enter fullscreen mode Exit fullscreen mode

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);
  }
}
Enter fullscreen mode Exit fullscreen mode
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);
  }
}
Enter fullscreen mode Exit fullscreen mode

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))
      }));
    }));
Enter fullscreen mode Exit fullscreen mode

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.

resulting tables

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.

  1. When the initial empty arrays are emitted by the BehaviorSubjects
  2. When the courses array is emitted
  3. When the students array is emitted

console output

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.

šŸ’– šŸ’Ŗ šŸ™… šŸš©
juliandierkes
Julian Dierkes

Posted on August 9, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related