Check out myAngular article series with live demos and Facebook group Angular - Advanced Topics

Observables in Angular

Torgeir "Tor" Helgevold
- JavaScript Developer and Blogger
Published: Wed Jan 06 2016

The RxJs community has presented the idea that any series of events can be modeled as one or many asynchronous or synchronous arrays. In the following post I want to explore this by modeling a series of different user inputs as Observables.

I am still learning about Observables and their potential, but I figured it would be interesting to implement a custom text editor, from scratch, using Observables to represent keyboard and mouse events.

You can test out the editor here.

Building a perfect text editor is not really the point here, but I want to see if there is any added value from looking at input sequences as Observables. The first step when building a text editor is identifying which input events to support. In my sample I have decided to focus on adding the ability to input and delete characters. Currently I have limited the input to letters a-z and numbers, but you can obviously extend this by mapping a larger character set. From there I also wanted to add support for mouse events for cursor placement and basic text selection.

Part of my approach has been to see if there is a way to capture these input events and combine them into correlated streams.

Key Events

I will start with keyboard input since that is probably the easiest form of input.

this.keyUp = Observable.fromEvent(document,'keyup') .filter((k:any) => this.isSuportedCharacter(k.which)) .map((k:any) => { return {operation:'modify',character:new Character(k.which),element:k}; });

In the sample above I am creating a stream of key strokes by trapping keyup events. I then send the trapped events through a filter operator to remove any unsupported characters. This is necessary since I don't have support for all possible characters at this point. Lastly I map the events to a custom object model that will be passed on to the subscriber of this Observable.

Next I have added another Observable for keydown events. This is there to silence the effects of characters like spacebar and backspace, which would otherwise cause the browser page to jump.

//Prevents page jumping this.keyDown = Observable.fromEvent(document,'keydown') .filter((k:any) => k.which === KeyMap.spaceBar || k.which === KeyMap.backSpace) .map((k:any) => { return {element:k}; });

One of the issues I've had is coming up with a clean way to start an Observable in Angular. One of my complaints about my code so far is that I have to make a DOM reference as a starting point for the Observable. I am not really sure how to fix that at this point, but maybe there will be an abstraction for it at some point.

Mouse Events

Next up is mouse click which will add support for placing the cursor in arbitrary locations in the document.

this.click = Observable.fromEvent(this.editor,'click') .map((e:any) => this.getCharacterFromElement(e,'select')) .filter(e => e !== null);

Same as before, I am mapping a clicked character in the editor back to my object model, but I am filtering out click events on non characters (represented by null).

So far the events have been fairly simple since there have been no dependencies between events. However, this is no longer the case when we move to text selection. If you think about it, text selection is initiated by a mousedown, then lasts for as long as you have mousemove events until you end it with a mouseup event.

This may seem like a complicated series of events, but it turns out that we can describe it fairly easily with Observables.

this.mouseDown = Observable.fromEvent(this.editor,'mousedown') .do(e => this.currentDocument.clearSelection(this.getCharacterFromElement(e,'range'))) .flatMap((m) => Observable.fromEvent(this.editor,'mousemove')) .map((e:any) => this.getCharacterFromElement(e,'range')) .filter(e => e !== null) .takeUntil(Observable.fromEvent(this.editor,'mouseup')).repeat();
The above Observable starts by trapping mousedown, then flattens multiple mousemove events into a single stream before ending it all with a mouseup event. The final repeat allows us to repeat the process over and over again.

What I love about this is how easy it is to correlate events and map them into a common character based object model. The fact that we are dealing with events is almost completely abstracted since this feel more like functional array code than event handlers.

The last step is to bring all these Observable into one happy family of Observables that can be subscribe to.

this.keyUp .merge(this.click) .merge(this.mouseDown) .merge(this.keyDown).subscribe(e => { this.currentDocument.processInput(e.character,e.operation); e.element.preventDefault(); });

As you can see I am able to merge all the different streams into a single stream of events that can be subscribed to.

Anyway, this was just a simple attempt at demonstrating some of the cool things you can do with Observables. As I mentioned above, I would really like a better way to initiate the creation of the initial Observable – without DOM references.

As always you can find my code on Github, but you can also check out my demo editor here.

If you liked this article, share it with your friends on social media:

We also have a new Facebook group about advanced Angular topics.

I invite you to follow me on twitter