fbpx

I wrote about RxJS and Observables in my last post by drawing comparing it with traditional Javascript promises. I mentioned in that post that I was going to explore Observables more in depth in the future and this is one of those posts. In this post, I will talk about Observables and creation & transform RxJS Operators. The functions we will discuss are pure functions such as filter, map, partition, pipe with code samples of how to use them.

Background

I have been working with RxJS operators for a while now and I think it has a steep learning curve. However once I got past that, it was all blue skies and sunshine. Understanding its main attraction – Observables, is one of the best things I have discovered in recent times. You can know more about that in either one of my earlier posts or the rxjs docs.

I first got to know Observables when I was building the now “on-hold” e-commerce app for the startup 10POiNT2. I was building it using the Ionic framework. It took me a while to understand how it all fits together for Observables. I mean, getting to the point of figuring out how to filter an array of Observable took a while. However, like learning any Java EE frameworks from the early 2000s, once you get past the learning curve, everything becomes easy. A precursor to understanding Observables is to understand its operators.

Creation & transform RxJS Operators

RxJS has a wealth of operators all of which can be divided in to their own categories. The categories are defined by the nature of the operations performed by the operators. Let’s declare a few variables that we will be working with to understand the concepts in this post.

  item1 = "Micro SD card";
  item2 = "M.2 ssd";
  item3 = "3200 mhz RAM";

Creation operators

Just as the name suggest, operators in this category are those which can create/make an Observable for us. Below are a few of the operators from the creation category.

from

It converts an array, object or a promise into an Observable and emits each value from it.

  fromItemObs() {
    let itemObs = from([
                  this.item1, 
                  this.item2, 
                  this.item3]);
    console.log("about to subscribe to values");
    itemObs.subscribe(v => {
      console.log(v);
    });
  }

of

It converts each argument into an Observable sequence, unlike “from” it emits each argument as an element instead of flattening the array.

ofItemObs() {
    //see how we are passing each element & not array
    let itemObs = of(this.item1, this.item2, this.item3);
    console.log("about to subscribe to 'of' values");
    itemObs.subscribe(v => {
      console.log(v);
    });
  }

Utility operators

A collection of handy operators that make working with RxJS easier. Operators like,

tap

This one is interesting and a little different, not sure how to best explain it but I will try. So what happens with this operator, that every time an Observable emits a value, this operator can intercept it and do something to it. Formally, it can perform side-effects on the observed data without actually modifying the observed data. Hmm… you can also think of it as the Array.forEach function for an array over time. More on this in a later post.

Transformation operators

partition

For this one, I reckon the name says it all i.e. partition. This operator takes an observable and splits it into two. So give an Observable and turn that into two Observables and return them.

For this one, let’s change the source variables a little.

  item1 = {name:"Micro SD card", type: "storage"};
  item2 = {name:"M.2 ssd", type: "storage"};
  item3 = {name:"3200 mhz RAM", type: "memory"};

Following that, out partition code would be as follows,

partitionItemObs() {
    let itemObs = of(this.item1, this.item2, this.item3);
    const [storageItems, memoryItems] = partition(
      itemObs,
      (value, idx) => value.type == "storage"
    );
    console.log("About to print storage items");
    storageItems.subscribe(v => {
      console.log(v.name);
    });
    console.log("About to print memory items");
    memoryItems.subscribe(v => {
      console.log(v.name);
    });
  }

map

Similar to the Javascript map function, the map function applies a given function to every value emitted from an Observable and emits the resulting value as an Observable.

Let’s change the source variables again.

  item1 = {name:"Micro SD card", type: "storage", cost: 20};
  item2 = {name:"M.2 ssd", type: "storage", cost: 129};
  item3 = {name:"3200 mhz", type: "memory", cost: 79};

Now, say we want to offer 10% discount on all items.

async mapItemObs() {
    let itemObs = of(this.item1, this.item2, this.item3);
    let modifiedObs = await itemObs.pipe(
      map(val => {
        let discountPrice = val.cost - (val.cost * 10) / 100;
        return {name: val.name, type: val.type, cost: discountPrice};
      })
    );
    console.log("About to print items with 10% discount");
    modifiedObs.subscribe(v => {
      console.log(v);
    });
  }

I have intentionally kept code verbose so it’s easily understood.

and…

In addition to the aforementioned operators, there are other operators that can help with creation, mathematical, utility operators to name a few. I will talk about them in a different post.

Can we use more than one operator?

Yes, what if we need to apply more than one operator to an Observable? Yes we can do that, but we can only do that to pipeable operators. Pipeable operators are those that can be chained.

https://rxjs-dev.firebaseapp.com/guide/operators

We already saw the use of pipe operator. To show how we can use it, let’s try something. Let’s say we only want items of type storage with a 10% discount? Let’s see how we can do that,

  pipeOps() {
    let itemObs = of(this.item1, this.item2, this.item3);
    const storageDiscount = itemObs.pipe(
      filter(item => item.type == "storage"),
      map(item => {
        let discount = item.cost - (item.cost * 10) / 100;
        return { name: item.name, type: item.type, cost: discount };
      })
    );
    console.log("ABOUT TO PRINT DISCOUNTED SSTORAGE ITEMS");
    storageDiscount.subscribe(item => console.log(item));
  }

Simple, right? Once you get the hang of it, rxjs operators are a lot of fun to work with.

I wrote all of the above code in an Angular project’s app.component.ts file and here’s the entire code.

import { Component } from "@angular/core";
import { from, of, partition } from "rxjs";
import { map, filter } from "rxjs/operators";

@Component({
  selector: "my-app",
  templateUrl: "./app.component.html",
  styleUrls: ["./app.component.css"]
})
export class AppComponent {
  item1 = { name: "Micro SD card", type: "storage", cost: 20 };
  item2 = { name: "M.2 ssd", type: "storage", cost: 129 };
  item3 = { name: "3200 mhz", type: "memory", cost: 79 };

  constructor() {
    this.fromItemObs();
    this.ofItemObs();
    this.partitionItemObs();
    this.mapItemObs();
    this.pipeOps();
  }

  fromItemObs() {
    let itemObs = from([this.item1, this.item2, this.item3]);
    console.log("about to subscribe to values");
    itemObs.subscribe(v => {
      console.log(v);
    });
  }
  ofItemObs() {
    //notice, here, we are passing each element as an individual item
    let itemObs = of(this.item1, this.item2, this.item3);
    console.log("about to subscribe to of values");
    itemObs.subscribe(v => {
      console.log(v);
    });
  }
  partitionItemObs() {
    let itemObs = of(this.item1, this.item2, this.item3);
    const [storageItems, memItems] = partition(
      itemObs,
      (value, idx) => value.type == "storage"
    );
    console.log("About to print storage items");
    storageItems.subscribe(v => {
      console.log(v.name);
    });
    console.log("About to print memory items");
    memItems.subscribe(v => {
      console.log(v.name);
    });
  }
  async mapItemObs() {
    let itemObs = of(this.item1, this.item2, this.item3);
    let modifiedObs = await itemObs.pipe(
      map(val => {
        let discountPrice = val.cost - (val.cost * 10) / 100;
        return { name: val.name, type: val.type, cost: discountPrice };
      })
    );
    console.log("About to print items with 10% discount");
    modifiedObs.subscribe(v => {
      console.log(v);
    });
  }
  pipeOps() {
    let itemObs = of(this.item1, this.item2, this.item3);
    const storageDiscount = itemObs.pipe(
      filter(item => item.type == "storage"),
      map(item => {
        let discount = item.cost - (item.cost * 10) / 100;
        return { name: item.name, type: item.type, cost: discount };
      })
    );
    console.log("ABOUT TO PRINT DISCOUNTED SSTORAGE ITEMS");
    storageDiscount.subscribe(item => console.log(item));
  }
}

Like the blog? Subscribe for updates

As usual, if you find any of my posts useful support me by  buying or even trying one of my apps on the App Store. 

https://mydaytodo.com/apps/

Also, if you can leave a review on the App Store or Google Play Store, that would help too.


0 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *