Ver en Español
Implementing filter as an operator in Observables
Apr 14, 2023
Updated: Jun 25, 2026

Implementing filter as an operator in Observables

In this post we're going to explore how to grow the number of operators on an Observable by adding the filter operator.

Remember from the previous post, where we implemented the map operator on Observables from scratch, much like RxJS does. Now we're going to increase the number of operators we can use on an Observable.

First, let's take a look at the purest definition of an Observable, with a generator function that builds observables from events:

// Define a class called Observable
class Observable {
  // The class constructor receives a subscribe function
  constructor(subscribe) {
    // Store the subscribe function in a private property
    this._subscribe = subscribe;
  }
  // Method to subscribe an observer to the observable
  subscribe(observer) {
    // Run the subscribe function with the provided observer
    return this._subscribe(observer);
  }
  // Static method that creates an observable from a DOM event
  static fromEvent(domElement, eventName) {
    // Return a new Observable instance
    return new Observable(function subscribe(observer) {
      // Define an event handler that calls the observer's next method with the event
      const handler = (ev) => {
        observer.next(ev);
      };
      // Add the event handler to the DOM element for the given event name
      domElement.addEventListener(eventName, handler);
      // Return an object with a method to cancel the event subscription
      return {
        unsubscribe() {
          // Remove the event handler from the DOM element
          domElement.removeEventListener(eventName, handler);
        },
      };
    });
  }
}

With this, you can create Observables that return event information like so:

// Get a reference to the button with ID "button" from the HTML document
const button = document.getElementById("button");

// Create an observable called "clicks" using the static method "fromEvent" of the Observable class,
// subscribing the button to the "click" event
const clicks = Observable.fromEvent(button, "click");

// Subscribe an observer object to the "clicks" observable and store the subscription in "clicksSubscription"
const clicksSubscription = clicks.subscribe({
  // Define the observer's "next" method to handle click events on the button
  next(e) {
    // Log a message indicating the button was clicked and show the event object "e"
    console.log("Click in the button", e); // "e" holds all the event information
  }
});

// Cancel the subscription to button click events using the "unsubscribe" method of the subscription object
clicksSubscription.unsubscribe(); // This is how you can unsubscribe

But sometimes we need to perform more operations over the data stream. For example, if we want to get only some data from the event, like the mouse position, or if we only want to listen to clicks made on the left side of the button. To do that, we're going to implement the filter function over Observables.

filter example in Arrays

Let's see how filter works on JavaScript Arrays before implementing it on Observables:

[1, 2, 3, 4].filter((x) => x > 3);
// returns [4]

filter, just like map, receives a function as a parameter. That function is a validation function and returns true or false. The filter operator returns the element for which the validation function is true.

Implementing filter in Observables

Remember that we defined the map method in the previous post, so here we only add the filter method to the same Observable class:

// Define a "filter" method that takes a validation function as an argument
filter(validationFunction) {
  // Store a reference to the current object in the "self" variable
  const self = this;
  // Return a new observable
  return new Observable(function subscribe(observer) {
    // Subscribe an observer object to the current observable ("self") and store the subscription in "subscription"
    const subscription = self.subscribe({
      // Define the observer's "next" method to handle the values emitted by the observable
      next(v) {
        // If the validation function returns true for the value "v", pass the value to the observer's "next" method
        if (validationFunction(v)) {
          observer.next(v);
        }
      },
      // Define the observer's "error" method to handle errors emitted by the observable
      error(e) {
        // Pass the error "e" to the observer's "error" method
        observer.error(e);
      },
      // Define the observer's "complete" method to handle the completion of the observable
      complete() {
        // Call the observer's "complete" method
        observer.complete();
      }
    });
    // Return the subscription
    return subscription;
  });
}

Now let's see how to use the filter operator on Observables to filter the clicks made on the left side of the button. Since filter and map return a new Observable, we can chain them directly:

// Get a reference to the button with ID "button" from the HTML document
const button = document.getElementById("button");

// Create an observable called "clicks" using the static method "fromEvent" of the Observable class,
// subscribing the button to the "click" event
const clicks = Observable.fromEvent(button, "click");

// Filter and transform the click events before subscribing an observer object
const clicksSubscription = clicks
  // Apply the "filter" method to allow only events where the mouse X position is less than 40
  .filter((ev) => ev.offsetX < 40)
  // Apply the "map" method (implemented in the previous post) to transform the event into its mouse X position (offsetX)
  .map((ev) => ev.offsetX)
  // Subscribe an observer object to the filtered and transformed events
  .subscribe({
    // Define the observer's "next" method to handle click events on the button
    next(offsetX) {
      // Log a message indicating the mouse X position at the moment of the click
      console.log("Mouse position relative to X " + offsetX); // Only prints if you click to the left of "Me"
    },
  });

Here is the complete example with the filter and map implementation:

https://codepen.io/seagomezar/pen/WdVzxB

Note: verify that the CodePen link is still live before publishing; old short IDs sometimes stop existing.

Conclusions

  1. We've learned to implement the filter operator on Observables to filter and process the events that meet certain criteria.
  2. Operators on Observables, like filter and map, let us manipulate and perform operations over the data stream more efficiently.
  3. Operators over Observables follow a similar logic and, for the most part, must return a new Observable. The operations or filters over the values are done in the next function.

Exercises to practice

  1. Create an Observable that filters and shows only the clicks made with the right mouse button.
  2. Implement an Observable that filters and shows only keyboard events with arrow keys (up, down, left, and right).
  3. Combine the filter and map operators to create an Observable that filters keyboard events and transforms the output into an object that holds relevant information about the pressed key (for example, the key name and whether it was pressed together with a modifier key like Shift, Ctrl, etc.).

3-point summary

  1. We learned to implement and use the filter operator on Observables.
  2. We understood how operators on Observables, like filter and map, work to manipulate events.
  3. We saw examples of how to apply filter and map in real use cases, like filtering clicks and keyboard events.

Additional ideas to apply what you learned

  • Implement an Observable that filters and shows the user's scroll events on a web page, but only when the scroll goes upward.
  • Create an Observable that detects and shows change events (change) on a form, but only when the entered value meets certain conditions, like a minimum length or a specific format.
  • Combine the filter and map operators to create an Observable that detects mousemove events and transforms the output into an object that holds relevant information about the mouse position as percentages of the window width and height.
  • Use the filter function on Observables to implement an autocomplete system that shows suggestions based on the user's input in a text field, but only once the user has typed at least three characters.
  • Implement an Observable that filters and shows window size change events (resize), but only when the difference between the previous size and the new size is greater than a specific value.

These extra ideas will help you apply and consolidate what you learned about implementing filter on Observables. Don't forget that you can combine different operators to achieve more complex results tailored to your needs.

That's all, I hope this post is useful to you and that you can apply it to a project you have in mind. Leave me a comment if it helped or if you have any questions, and if you liked it, share it using the social links below. Good luck with your projects and keep exploring the possibilities that Observables offer!

Sebastian Gomez

Sebastian Gomez

Creador de contenido principalmente acerca de tecnología.

Leave a Reply

0 Comments

Advertisements

Related Posts

Categorias