- Update: Since this post was written, Angular template code has changed. Updated documentation can be found at Angular.io. RxJS has largely remained the same and so have Observables but there are more modern ways to import RxJS and leverage it so make sure to read their latest documentation here.
Interested in learning about the latest verison of Angular? Here is everything you need to know.
Angular 2 embraces elements of reactive programming, a paradigm focused on how data flows through a web application. To follow the reactive approach, we use Observables to build services and components that can react to changes in data across our application. Observables aren't exactly an Angular 2 specific feature, they're a part of the JavaScript Reactive Extensions library (RxJS) and are set to be an included feature in the release of ES7. In this post, we'll explore the motivation for Observables and the concepts of Reactive Programming, and how to use Observables in your application patterns.
Why Observables?
The main purpose of using Observables is to observe the behaviour of a variable. In an imperative way, a variable is only changed when its state is mutated by assigning a new or updated value. The reactive approach allows variables to develop a history of evolving change, asynchronously being updated to the values of other points of data.
Let's take a look at how both approaches differ — consider capturing the mouse movements in a browser window. The imperative way might look something like this:
let mouse;
document.onmousemove = (e) => mouse = [e.pageX, e.pageY];
Anytime we get an event that the mouse has moved, we re-assign mouse to the updated coordinates. The state of mouse is discrete in that each re-assignment overrides the previous one, removing any continuous connection.
Now let's see the reactive way:
let mouse = document.onmousemove.subscribe()
Here mouse is only assigned once and the values it receives are stored in a stream of continuous activity. The values captured by mouse are stored in a what amounts to a FIFO queue; they can be iterated on using standard array operations. Pretty cool stuff, unfortunately, onmousemove is not actually an observable we can subscribe to, so this code example won't work in current browsers — yet. ES7 is being released this year and will include built-in support for Observables - can't wait!
In the meantime, RxJS provides an implementation that we can use right away.
Anatomy of an Observable
Observables follow the Observer pattern, where a single piece of state (the Observable) is watched by one or more Observers which can react as it changes over time.
At a low level, the Observable acts as an event emitter, sending a stream of events to any subjects that have subscribed to it. The Observable object doesn't contain any references to the subject classes itself: instead, an event bus is used to decouple publishing and subscribing concerns. What we get is an asynchronous stream of values that can be operated on using common iteration patterns -- exactly the sort of thing that will help us tackle our data concerns using a reactive methodology.
Using Observables in Angular 2 Components
The great advantage of reactive programming is the ability to connect points of data together. A common task when building any application is to determine the way your data will travel from a network request or user input to the visual UI components on the screen. This is where reactive programming shines.
In Angular 2, our components have access to pipes, which can be used to transform values via simple expressions in our templates. Pipes are very powerful tools and we'll use them to connect component data across our application. A common way of separating components is to break the design up into dumb and smart components. A dumb component's only job is to present data to the UI view and emit any UI events, and a smart component is concerned with what the dumb components should be displaying. Using Observables and AsyncPipe we can set up a bridge of data to flow between our smart and dumb components. Smart components can pass data to display, and the dumb components can pass any input events that they capture from the UI.
How does this look in action? Well, let's build out a simple Spotify search component. We'll need a dumb component that will capture user input, and display any search results that have come in. What will our smart component do? It will use the HTTP service to get search results and apply any transformations to the data that we need.
Before we begin, to use Observables you need to import the rxjs dependency into your Angular 2 project. Our examples here will use a hosted CDN to do that, but if you're using webpack or browserify for your project then just include the rxjs node module in the dependencies field of your package.json and import it into your app code. You can view the entire working example here. OK sweet, let's get started by looking at our dumb component!
Dumb Component
import {Control, FORM_DIRECTIVES} from 'angular2/common';
import {Component, Output, Input, EventEmitter} from 'angular2/core';
import 'rxjs/Rx';
@Component({
selector: 'spotify-search',
directives: [FORM_DIRECTIVES],
template: `
<h1>Spotify Search</h1>
<input
[ngFormControl]="searchBox"
placeholder="Search Spotify artist" />
<div *ngFor="#artists of results | async">
{{ artists.name }} -- Popularity {{ artists.popularity }}
</div>
`
})
export class SpotifySearch {
@Input() results: Observable<any>;
@Output() searchEvent: EventEmitter = new EventEmitter();
private searchBox:Control = new Control();
constructor() {
this.searchBox
.valueChanges
.debounceTime(200)
.subscribe((event) => this.searchEvent.emit(event));
}
}
Here, in all its glory, is our dumb component - responsible for displaying UI elements and capturing UI events. We have a list of artists to display, which comes from an Observable named results that is decorated as an Input, meaning it will come from the parent smart component. These results are coming in as an array of artist objects, so we can iterate through each one and display some properties using the *ngFor directive. You'll notice that in our expression we utilize the async pipe, which will subscribe to any values that are being emitted from results automatically. The pipe maintains a subscription to results and keeps delivering values as they arrive. What's great with this pattern is the decoupling of data concerns from UI concerns. By providing an Observable to interface with this dumb component, our data can come from a service, a redux store, or any other abstraction we want to model our data with. As long as we push our data through the Observable stream, our dumb component will display it. Pretty sweet huh?
Our dumb component also has an input field, which makes this component responsible for capturing events generated by the user whenever they change the value of the input field. Since our smart component is the one that fetches data and transforms it, we need to let it know that some new search criteria has been entered. So, we create an EventEmitter and decorate it as an Output coming from this component. An EventEmitter uses Observables as its underlying mechanism but uses an Angular 2 specific adapter. This isn't really much of a concern for us, the EventEmitter has a subscribe and emit function used to observe and emit data respectively. We also use debounceTime to debounce the volume of events that will be fired whenever a user types something into the input field. This is good practice - we don't want our application to slow down if the user types away rapidly in our input field.
With the dumb stuff out of the way, let 's take a look at our smart component.
Smart Component
import {Http} from 'angular2/http';
import {Component} from 'angular2/core';
import {Input, Output, EventEmitter} from 'angular2/core';
import 'rxjs/Rx';
import {Observable} from 'rxjs/Observable';
import {SpotifySearch} from './services/Search';
@Component({
selector: 'app-root',
directives: [SpotifySearch],
template: `
<spotify-search
(searchEvent)="onSearch($event)"
[results]="data">
</spotify-search>
`
})
export class AppComponent {
private data: Observable;
private dataObserver: Observer;
constructor(private http: Http) {
this.data = new Observable(observer => this.dataObserver = observer);
}
onSearch(event) {
this.http.get(
'https://api.spotify.com/v1/search?q=' + event + '&type=artist'
).map((response) => {
var artists = response.json();
return artists.artists.items;
}).subscribe(result => {
this.dataObserver.next(result);
}, error => console.log('Could not load artists'));
}
}
OK, so our smart component is going to control what our spotify-search component is going to display, as well as handle any new search criteria being emitted from spotify-search. We'll fetch data directly in this component for the sake of simplicity, but it would be best to abstract any calls to the Spotify API in a re-usable and injectable service. Better yet -- using redux to keep our data in a single source of truth store would be great too, but that's a different article for a different day. Let's focus on how to direct our data concerns in this smart component down to the view concerns in our dumb component. To do that, we create a new variable, data and assign it a new Observable and bind it to the results input field in spotify-search. This will ensure that any new values we emit to this Observable will show up in spotify-search. We also create an Observer, assigned to dataObserver, and set it to be the Observer used in our data Observable. Why do we do this? Well, the dataObserver is the object responsible for generating the values that will be emitted through the Observable. We need to have an explicit reference to it in order to push through any data we get from our HTTP requests.
Now that we have the ability to send data to our dumb component, we need to know when to actually search for data. This happens with onSearch, which is bound to the searchEvent output property in spotify-search. When an event is emitted, onSearch gets called with the value emitted by the EventEmitter Observable stream (in this case, the value of the search input). With this search criterion, we use http.get to query the API for relevant search results, which also uses an Observable to subscribe to the incoming network response. In getting our response, we use map to transform the contents of our data. You may have used map before; it creates a new array with the results of calling a provided function on every element in that array. Our HTTP request only returns a single value, so our Observable stream will only have one element that map will iterate over. We transform this item into an object suitable for consumption by converting the response object into a JSON object, and returning the artists.items property, which contains an array of the artist objects we want to be displayed. Finally, we push this data through our data Observable stream by using the dataObserver's next() method. Ta-da! So there you have it! Our smart and dumb components are now connected through the power of Observables, which allows our data to flow from the areas of our application concerned with data, to the one concerned with UI views. It's a beautiful thing.
Conclusion
So what have we learned? Reactive programming offers an interesting approach to managing the way in which data evolves over time, and how it can be consumed across your application. We implemented some of these ideas using the smart/dumb component pattern in Angular 2 and got a pretty nifty Spotify search component working. Interesting stuff! Now get out there build something with these new fancy Observables!