Build Your Own Observable Part 1: Arrays

Nate Lapinski
Jul 11, 2018 · 4 min read

Angular uses RxJS observables. I can’t understand something until I create it with my own hands. Perhaps you are the same. Either way, let’s build an observable from scratch!

When I first started working with RxJS, I didn’t know what I was doing. Now, I’m able to solve problems using observables, and I can even read the RxJS sources with confidence. It’s definitely something you can do too.

The only catch is, to understand observables, you have to first understand arrays.


Arrays

We’ll see pretty shortly that observables are basically the same mental model as arrays, just with an added dimension of time. But, that’s not going to do you any good if you don’t already feel comfortable with arrays.

Start

Here’s a task for you: Create an array in Javascript that contains the numbers 1, 2, and 3.

Now, go ahead and write some code that adds one to each of those values, and returns the results in a new array.

Perfect. Array.prototype.map is the magic that lets us do this. It takes a projection function, in this case vale => value + 1 and applies it to every value in the array, then returns a new array. You could implement it by hand, if you really wanted to:

Nothing magic about that. We simply loop over the elements in the array, and apply the projectionFn to each element, and push the results of that into a new array. We use this here to reference the array, since we are calling map off of Array.prototype. [1,2,3].map(v => v + 1)

Let’s cover filter next. Maybe now you want to filter the array to return only even numbers:

filter takes a predicate function, in this case v => v % 2 === 0, and applies that to every element in the array. Elements which return true when passed into the predicate will be added to the returned array. Let’s implement filter as well:

Again, very simple implementation.

The goal here is to convince yourself that nothing magical is happening with these Array.prototype functions. They aren’t some blackbox — you could write them yourself if you wanted to. By familiarizing yourself with their implementations, it will be that much easier to implement the same functions on observables later.

Now that we have both map and filter, we can do some sweet composition!

Notice how our map and filter methods always return arrays. This is a very important point for composing array operations. If they returned something other than an array, like undefined or null or 567 then we would no longer have access to Array.prototype, and therefore would not be able to compose any further.

For example,[1,2,3].map(x => x + 1).567.filter(x => x > 2) doesn’t end well, since 567 doesn’t have a filter method (since it isn’t an array, and that’s what we are trying to compose).

Composition is a fascinating topic. MPJ has a nice series on Youtube which you can find here, and Eric Elliott has a great series on the topic as well. As we’ll see later, observables are composable as well.

You’ve read this far and we’re still talking about arrays? I promise we’ll get to observables soon enough, but there’s one more observation we need to take from arrays. Once you have that, observables will be trivial.

What does this code return?

On line 30, we map over the gameData array. map always returns an array. Always. However, inside of the call to map, we return game.bosses, which is also an array, so we end up with a nested array. [[{ …boss data…}]].

This is probably not what we wanted. Ideally, we’d like to have everything flattened into a single array. We simply need a method which will take an array, and reduce its depth by a factor of one. Since we have a single layer of nesting in this array, we would want to apply this depth-reducing method once.

Unfortunately, Array.prototype does not currently have a good way of doing this. Array.prototype.concat can be used, although it is not ideal for this situation. In the future, we’ll have access to flat and flatMap, but that doesn’t do us much good for now, so let’s implement our own flatten function. It will return a new array with depth reduced by a factor of one.

Now, we can go back to our example from above, and apply flatten to the nested game.bosses array returned from map:

A map followed by a flatten is such a common set of operations, that most languages combine the two into a flatMap operator.

Being able to delve down a layer with map, and then come back up a layer with flatten is a very useful skill to have when working with nested data structures, such as arrays. This comes up on a regular basis when working with higher-order observables as well (observables of observables). If you’ve ever found yourself dealing with nested observables, and reaching for the flatMap operator, then you’ll know what I mean.


That’s it for part one. If you’re feeling comfortable with all of these array methods, then it’s time to make everything asynchronous with observables.

Summary

  • Array methods like map and filter are great for composition
  • Those methods aren’t mythical creatures — you could easily write them yourself
  • When you have nested arrays, you need something more sophisticated, like a flatMap.
  • As we’ll see, these concepts carry over to observables nicely.

Part 2: Containers and Intuition

Nate Lapinski

Written by

Fullstack Developer. Love digging into the internals of stuff. Always trying to reach the next level. @nate_lapinski on Twitter. Writer for AngularInDepth.