|
| 1 | +The `zip()` operator in RxJS is a **combination operator**. Its job is to combine multiple source Observables by waiting for each observable to emit a value at the **same index**, and then it emits an array containing those values paired together. |
| 2 | + |
| 3 | +## Analogy: The Zipper |
| 4 | + |
| 5 | +Think of a clothing zipper. It has two sides (or more, if you imagine a multi-way zipper!). To close the zipper, teeth from _both_ sides must align at the same position. `zip()` works the same way: |
| 6 | + |
| 7 | +1. It subscribes to all the source Observables you provide. |
| 8 | +2. It waits until the **first** value arrives from **every** source. It then emits these first values together in an array: `[firstValueA, firstValueB, ...]`. |
| 9 | +3. Then, it waits until the **second** value arrives from **every** source. It emits these second values together: `[secondValueA, secondValueB, ...]`. |
| 10 | +4. It continues this process, index by index (0, 1, 2,...). |
| 11 | +5. **Crucially:** If one source Observable completes _before_ another, `zip()` will stop emitting new pairs as soon as it runs out of values from the shorter source to pair with. It needs a value from _all_ sources for a given index to emit. |
| 12 | + |
| 13 | +## Why Use `zip()`? |
| 14 | + |
| 15 | +You use `zip()` when you have multiple streams and you need to combine their values based on their **emission order or index**. You specifically want the 1st item from stream A paired with the 1st from stream B, the 2nd with the 2nd, and so on. |
| 16 | + |
| 17 | +## Real-World Example: Pairing Related Sequential Data |
| 18 | + |
| 19 | +Imagine you have two real-time data feeds: |
| 20 | + |
| 21 | +1. `sensorA$` emits temperature readings every second. |
| 22 | +2. `sensorB$` emits humidity readings every second, perfectly synchronized with sensor A. |
| 23 | + |
| 24 | +You want to process these readings as pairs (temperature and humidity for the _same_ timestamp/interval). `zip` is perfect for this. |
| 25 | + |
| 26 | +Another scenario (less common for APIs, more for UI events or other streams): Suppose you want to pair every user click with a corresponding item from another list that gets populated sequentially. The first click pairs with the first item, the second click with the second, etc. |
| 27 | + |
| 28 | +## Code Snippet Example |
| 29 | + |
| 30 | +Let's create a simple Angular component example using `zip`. We'll zip together values from two simple streams: one emitting letters ('A', 'B', 'C') quickly, and another emitting numbers (10, 20, 30, 40) more slowly. |
| 31 | + |
| 32 | +```typescript |
| 33 | +import { Component, DestroyRef, inject, OnInit, signal } from "@angular/core"; |
| 34 | +import { zip, interval, of } from "rxjs"; |
| 35 | +import { map, take } from "rxjs/operators"; |
| 36 | +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; |
| 37 | + |
| 38 | +@Component({ |
| 39 | + selector: "app-zip-example", |
| 40 | + standalone: true, |
| 41 | + template: ` |
| 42 | + <h2>RxJS zip() Example</h2> |
| 43 | + <p>Combining letters and numbers based on index:</p> |
| 44 | + <ul> |
| 45 | + <li *ngFor="let pair of zippedResult()">{{ pair | json }}</li> |
| 46 | + </ul> |
| 47 | + <p> |
| 48 | + Note: The number stream had '40', but the letter stream completed after |
| 49 | + 'C', so zip stopped. |
| 50 | + </p> |
| 51 | + `, |
| 52 | + styles: [ |
| 53 | + ` |
| 54 | + li { |
| 55 | + font-family: monospace; |
| 56 | + } |
| 57 | + `, |
| 58 | + ], |
| 59 | +}) |
| 60 | +export class ZipExampleComponent implements OnInit { |
| 61 | + private destroyRef = inject(DestroyRef); |
| 62 | + |
| 63 | + zippedResult = signal<Array<[string, number]>>([]); // Signal to hold the result |
| 64 | + |
| 65 | + ngOnInit() { |
| 66 | + // Source 1: Emits 'A', 'B', 'C' one after another immediately |
| 67 | + const letters$ = of("A", "B", "C"); |
| 68 | + |
| 69 | + // Source 2: Emits 10, 20, 30, 40 every 500ms |
| 70 | + const numbers$ = interval(500).pipe( |
| 71 | + map((i) => (i + 1) * 10), // Map index 0, 1, 2, 3 to 10, 20, 30, 40 |
| 72 | + take(4) // Only take the first 4 values |
| 73 | + ); |
| 74 | + |
| 75 | + // Zip them together |
| 76 | + zip(letters$, numbers$) |
| 77 | + .pipe( |
| 78 | + // zip emits arrays like [string, number] |
| 79 | + takeUntilDestroyed(this.destroyRef) // Auto-unsubscribe |
| 80 | + ) |
| 81 | + .subscribe({ |
| 82 | + next: (value) => { |
| 83 | + // Update the signal with the latest pair |
| 84 | + // NOTE: For signals, it's often better to collect all results |
| 85 | + // if the stream completes quickly, or update progressively. |
| 86 | + // Here we'll just append for demonstration. |
| 87 | + this.zippedResult.update((current) => [...current, value]); |
| 88 | + console.log("Zipped value:", value); |
| 89 | + }, |
| 90 | + complete: () => { |
| 91 | + console.log("Zip completed because the letters$ stream finished."); |
| 92 | + }, |
| 93 | + error: (err) => { |
| 94 | + console.error("Zip error:", err); |
| 95 | + }, |
| 96 | + }); |
| 97 | + } |
| 98 | +} |
| 99 | +``` |
| 100 | + |
| 101 | +**Explanation of the Code:** |
| 102 | + |
| 103 | +1. `letters$` emits 'A', 'B', 'C' and then completes. |
| 104 | +2. `numbers$` starts emitting 10 (at 500ms), 20 (at 1000ms), 30 (at 1500ms), 40 (at 2000ms). |
| 105 | +3. `zip` waits: |
| 106 | + - It gets 'A' immediately, but waits for `numbers$` to emit. |
| 107 | + - At 500ms, `numbers$` emits 10. `zip` now has the first value from both ('A', 10) -> Emits `['A', 10]`. |
| 108 | + - It gets 'B' immediately, waits for `numbers$`. |
| 109 | + - At 1000ms, `numbers$` emits 20. `zip` has the second value from both ('B', 20) -> Emits `['B', 20]`. |
| 110 | + - It gets 'C' immediately, waits for `numbers$`. |
| 111 | + - At 1500ms, `numbers$` emits 30. `zip` has the third value from both ('C', 30) -> Emits `['C', 30]`. |
| 112 | +4. `letters$` has now completed. Even though `numbers$` emits 40 at 2000ms, `zip` cannot find a corresponding 4th value from `letters$`, so it stops and completes. |
| 113 | + |
| 114 | +## `zip()` vs. Other Combination Operators |
| 115 | + |
| 116 | +- **`combineLatest`**: Emits an array of the _latest_ values from each source whenever _any_ source emits. Doesn't care about index, just the most recent value from all participants. |
| 117 | +- **`forkJoin`**: Waits for _all_ source observables to _complete_, then emits a single array containing the _last_ value emitted by each source. Useful for running parallel one-off tasks (like multiple HTTP requests) and getting all results together at the end. |
| 118 | + |
| 119 | +Use `zip()` specifically when the _order_ and _pairing_ by index (1st with 1st, 2nd with 2nd, etc.) is what you need. |
0 commit comments