Understanding takeWhile
takeWhile mirrors values from the source observable to the observer as long as the predicate function—supplied as an argument—returns true. The operator subscribes to the source and starts forwarding emissions immediately.
Each time a value arrives, takeWhile invokes the predicate with that value and inspects the result. A true return causes the value to be passed downward; otherwise, the operator unsubscribes from the source, sends the complete notification to the observer, and terminates the stream.
The operator also accepts a second parameter that controls whether the offending value—the one that caused the predicate to return false—is delivered to the observer or discarded before completion.
Keep in mind that when you need to cap the number of emitted values rather than filter by condition, the take operator is the appropriate choice.
Here is the sequence of operations for takeWhile:
- Subscribe to the source observable
- For each incoming value, run the predicate function
- If the predicate yields a truthy result, forward the value to the observer
- If the predicate yields a falsy result, optionally forward the value (when inclusive mode is enabled), then unsubscribe from the source and send the complete notification
- Upon natural source completion, relay the complete notification to the observer
- Should the source throw, propagate the error notification to the observer
Practical Example
takeWhile is handy for tearing down a subscription and freeing resources when a specific condition is met. Imagine tracking mousemove events while the cursor is inside a box, and you want to stop listening the moment the cursor exits that box.
The implementation with takeWhile looks like this:
const box = createBox();
fromEvent(box, 'mouseenter').pipe(
exhaustMap(() => {
// we're only interested in the mousemove event
// as long as we're inside the box
// once we leave the box, remove the listeners
return fromEvent(box, 'mousemove').pipe(
takeWhile((event: MouseEvent) => {
return isEventInElement(
box.getClientRects(),
event.clientX,
event.clientY
);
})
);
})
).subscribe();
function isEventInElement(rect, clientX, clientY) {
if (clientX < rect.left || clientX >= rect.right) return false;
if (clientY < rect.top || clientY >= rect.bottom) return false;
return true;
}
function createBox() {
const box = document.createElement('div');
box.style.backgroundColor = 'blue';
box.style.position = 'fixed';
box.style.top = '100px';
box.style.left = '400px';
box.style.width = '300px';
box.style.height = '300px';
document.body.appendChild(box);
return box;
}
