RxJS Mastery – #26 zip

RxJS zip operator lesson title

The RxJS zip operator is a join creation operator. Therefore it’s combining multiple streams into one. The combination happens like a zipper on clothes. From each source one value is taken and emitted together with the other input values as a single value. Only after an emission the operator can consider further values of the input Observable. Hence, the emission happens at the time the slowest input Observable delivers its value.

RxJS zip explained 🎓

The zip operator subscribes to all input Observables and waits for a value from each of them until the values are then emitted as array. Then the waiting phase starts again. This cycle ends if one of the input Observables completes or errors out.

const frameworks$ = of('Angular', 'ReactJs', 'VueJs');
const yearOfIntroduction$ = of(2010, 2013, 2014);

const result$ = zip(
    frameworks$,
    yearOfIntroduction$,
);

result$.subscribe(console.log);
// [ 'Angular', 2010 ]
// [ 'ReactJs', 2013 ]
// [ 'VueJs', 2014 ]

If one of the Observables complete the one created by zip also completes and might ignore some values that couldn’t be combined (e.g. the AnotherSuperCoolFrameworkJS where we don’t know the year of introduction):

const frameworks$ = of('Angular', 'ReactJs', 'VueJs', 'AnotherSuperCoolFrameworkJS');
const yearOfIntrodution$ = of(2010, 2013, 2014);

const result$ = zip(
    frameworks$,
    yearOfIntrodution$,
);

result$.subscribe({ next: console.log, complete: () => console.log('complete') });
// [ 'Angular', 2010 ]
// [ 'ReactJs', 2013 ]
// [ 'VueJs', 2014 ]

The following drawing again visualises and shows the connection to the zipper. Only if two values can be combined an emission can happen, i.e. only if a number and a letter come together there will be an emission as a pair, i.e. [number, ‘letter’].

RxJs zip

What problems does the RxJS zip operator solve? 🚧

The zip operator can help if you have two data source where you exactly know that you want to combine the values of each in order. Furthermore the operator can also be used to simplify the handling around DOM events, e.g. mouse down and mouse up.

const documentEvent = eventName =>
    fromEvent<MouseEvent>(document, eventName).pipe(
        map((e: MouseEvent) => ({ x: e.clientX, y: e.clientY }))
    );

zip(documentEvent('mousedown'), documentEvent('mouseup')).subscribe(e =>
    console.log(JSON.stringify(e))
);

This way you could determine the coordinates of a drag operation (example from Learn RxJS).

How to test code using the zip operator🚦

Nothing special around testing here. Just remember that the slowest Observable dictates the output:

it('marbles testing', () => {
    testScheduler.run((helpers) => {
        const { expectObservable, cold } = helpers;

        const frameworks$ = of('Angular', 'ReactJs', 'VueJs');

        const result$ = zip(
            frameworks$,
            cold('a 200ms b 200ms (c|)', { a: 2010, b: 2013, c: 2014}),
        );

        expectObservable(result$).toBe(
            'a 200ms b 200ms (c|)',
            { a: ['Angular', 2010], b: ['ReactJs', 2013], c: ['VueJs', 2014]}
        );

    });
});

Exercise for the zip operator 💪

Combine the output of the zip values in a sentence. How can this be done without using any addition operator?

const frameworks$ = of('Angular', 'ReactJs', 'VueJs', 'AnotherSuperCoolFrameworkJS');
const yearOfIntroduction$ = of(2010, 2013, 2014);

const result$ = zip(
    frameworks$,
    yearOfIntroduction$
);

// Goal: `${framework} was introduced in ${year}`

As always the code examples and exercise solution can be found on GitHub.