Eddie Hinkle

Login

work

Thinking through Reactive Programming using RxJS

State can be a hard, difficult and bug inducing thing to track and manage. The best way to simplify state is to reduce it. Because of that, at my job, we try to embrace using Observable streams whenever we can. The difference between imperative programming and reactive (using Observables) programming is that in imperative programming you have to continually add a lot of logic around state and determining when and how state changes and making sure that state is reflected properly in the UI.

Reactive programming, focuses on streams of data (in our case called Observables). These streams of data can come from anywhere: mouse event, keyboard event, network request or even a timer running in the background. These streams can trigger events, return data, combine streams, split streams and transform existing data into new shapes and forms. But these streams (much like real life streams of water) have singular sources that are far away up at the top of the mountain. As such, when you are down stream, you aren’t having to make sure the water keeps running, you just install a hydroelectric damn to power your house.

Here is an example, that powers an auto-complete or auto-search function:

  1. this.autoResults$ = merge(of(undefined), fromEvent(this.searchField.nativeElement, ‘input’))
  2. .pipe(
  3. map(e => e !== undefined ? e.target.value : undefined), // Get value from input box
  4. debounceTime(350), // Prevent too many queries back to back
  5. distinctUntilChanged(), // Don’t run duplicate requests
  6. tap(() => { this.loading = true }), // show loading mask, this has to be in curly braces to avoid returning any data
  7. switchMap(searchTerm => this.service.search(searchTerm)), // Search for terms
  8. tap(() => { this.loading = false }),
  9. shareReplay(1) // Prevent different subscribers from creating duplicate parallel async requests
  10. );

The item above starts by merging two Observable streams. The first, is an Observable stream that will emit a single value: undefined. This is for one reason, and one reason alone. On the component loading, we want to immediately kick off a search for all the unfiltered results. So in the example above, undefined means we don’t have any search terms and we just want ALL of the results. It loads on component load, but if we enter keys into the search field, it will automatically update. We don’t have to assign any logic to watching the input field and changing a search term state when it’s been modified and then determining when and how to launch the search logic after the search term state is modified.

Instead, it’s a single stream that starts with events being emitted from the search field, and then all of our logic is built alongside that stream. To simplify things, If we ONLY want search results to show if we have entered search results, then it would look like this:

  1. this.autoResults$ = fromEvent(this.searchField.nativeElement, ‘input’)
  2. .pipe(
  3. map(e => e !== undefined ? e.target.value : ‘’), // Get value from input box
  4. filter(e => e.length === 0) // Don’t proceed if the string is empty
  5. debounceTime(350), // Prevent too many queries back to back
  6. distinctUntilChanged(), // Don’t run duplicate requests
  7. tap(() => { this.loading = true }), // show loading mask, this has to be in curly braces to avoid returning any data
  8. switchMap(searchTerm => this.service.search(searchTerm)), // Search for terms
  9. tap(() => { this.loading = false }),
  10. shareReplay(1) // Prevent different subscribers from creating duplicate parallel async requests
  11. );

Here, we start with an event, the search field’s input. We then pipe it through a series of filters, actions and transformations.

Step 1: Map. we convert the data object from a keyboard event into the value of the search field. If the event is undefined for some reason, we get an empty string.

Step 2: Filter. We filter based on the length of the string. We only want to search IF there are characters in the string. If we wanted we could make this set a minimum character limit to 2 or 3 so you don’t search after just 1 or 2 characters.

Step 3: Debounce. To prevent firing a bunch of requests WHILE we are typing the term, we only let the last emitted value through within a given time period. So in this case if during 200ms you type “Hello”. There were 5 emitted values in the stream.

H

He

Hel

Hell

Hello

In this instance, at 350ms, a single value is emitted, which is the last value received: “Hello”.

Step 4: DistinctUntilChanged. Suppose your search term is “Hello”. The results are showing. If you remove the last two characters “lo”, and then within 350ms you will have 4 new values emitted, but only one will make it past the Debounce: “Hello”. This is the same term you already have! Because of that, DistinctUntilChanged will prevent us from running the search logic on the same value.

Step 5: Tap. Tap is essentially to just run an action based on the data coming in. You can use it for all sorts of things: Logging, Analytics, or this case, updating a loading UI.

Step 6: SwitchMap. With a Switch Map we are switching from one observable stream to another in its same place. So in this instance, we are replacing the search term observable with the results from the search service observable.

Step 7: Tap. This tap isn’t called until the step before it is complete, which means when we get here, we have loaded all data from the network, so we can hide our loading UI.

Step 8: ShareReplay. Typically every Observable subscriber is subscribing to their own independent stream of data. Sometimes that is good, but in the regard of a network request like this that is always going to be the same data, it’s better to share the Observable among various subscribers. The “Replay” part of this is the fact that it will return the last value emitted to any late subscribers. This means if one subscriber starts following the stream and the search results happen, but after they are returned another component subscribes to the stream, it will get the last search result that happened (as well as any future results).

This is just a brief overview of one type of reactive/Observable pattern, but it's one I found helpful and thought I would share.

I've been working with Redux in Angular the last couple of days and I have to say I'm appreciating the unidirectional nature of it! Thinking of a couple more projects where I could use it.
Carla brought focus and hard-work to the ThreatConnect team. It was a pleasure working with her as she always brought solutions for problems that arose. She was always pleasant to work with both eager to help out others (including me) and never shy to ask for help when she ran into a problem herself. Carla fits the definition of a team player which can so often be rare in the world of software engineering. Her leaving will be a loss for our team, and it would be a pleasure to work on the same team as her in the future.

using git hooks for sanity

At my current job, we have two different build processes in grunt. The first is my development build which allows for easier debugging and the second is the production build that strips out code that isn't desired for the production servers. Unfortunately sometimes the development build runs just fine but the production build breaks. This leads to the potential mistake where I build something new using the development build but forget to check the production build before pushing my code into the shared git repo.

Nevertheless! I have a solution I've implemented and am documenting here for both myself and others.

I've added a pre-push git hook that runs a production build before pushing any branch to the server. What this means is I can continue to develop and commit as normal but when I try to push the code if it will break the production build it will break before the push allowing me to troubleshoot the issue, fix it and then do my push.

I've set this up in 2 ways, first I add a bash script called pre-push to (gitRepoDirectory)/.git/hooks. There is actually a big collection of potential hooks that you can choose from.

For me the script looks like this:

  1. !/bin/bash
  2. (npmProjectDirectory)/node_modules/grunt/bin/grunt --gruntfile (npmProjectDirectory)/Gruntfile.js build:prod

This works great using git on the command line. But seriously, who enjoys git cli? I use this great app called Git Tower. Unfortunately Git Tower can't find the node executable and thus it can't run my production build. This is an easy fix as detailed on their support page. After adding the environment.plist file from their support page, it works great!

No more accidentally breaking builds!

Pretty excited to have the last 3 months of my work rolled out today!
Please note: This site is in an active redesign. Some things might be a little off 🧐