Recently, I started on a project to migrate JavaScript to TypeScript in one of Lucidchart’s Angular 2 applications. This application has several components, each with its unique view. For those not familiar with Angular 2, a component is merely an encapsulation of a view on a webpage with its associated functionality and styling; e.g., HTML + JS + CSS. In our application, these components use the same data received from the backend, but present a unique view in each of them. The application’s performance and usability relies on the fastest possible availability of data. Since some of these components are rendered simultaneously, effective data-sharing was a good solution to greatly improve the user-experience.
Naive implementation
Let’s prototype this data-sharing service using TypeScript (JavaScript would look the same):
class SharingService {
private data1: CustomType1;
getData1():() => Promise {
if(goog.isDef(this.data1)){
return Promise.resolve(data1);
}
return Net.fetch().then(data => {
this.data1 = data;
return data;
});
}
}
@Component({
templateUrl: '.html',
selector:'custom-comp-foo',
})
export class CustomComp implements OnInit {
data1: CustomType1;
constructor(private sharingService: SharingService) {}
ngOnInit() {
this.sharingService.getData1().then(d => {
this.data1 = d;
});
}
}
There are several things to note in the above code snippet.
- There is a class
SharingService
which acts like the data-sharing service. - The
SharingService
class is injected into a componentCustomComp
using Angular 2’s dependency injection framework. - The data is obtained from the
SharingService
by theCustomComp
during its initialization (ngOnInit
).
The idea of a sharing service here is quite simple. It has a method getData1
that returns a Promise of the data you are interested in and a member variable data1
to store the data. Any other component that’s interested in data1
will have a resolved Promise ready to serve up data1
. The following figure better explains the flow of data:

While this implementation is straightforward, it’s not perfect. When data1
is fetched by a component (say component A) once, it remains the same throughout the lifetime of the component. When the sharing service fetches the data again for another component (say component B), this new data is not available for component A, unless component A polls for it or if component A is restarted. Communication between components A and B to know if the data needs to be loaded again can be painful, complicated, and difficult to scale.
Observables: Promises on Steroids
While a Promise represents a value to be resolved in future, an Observable represents a stream of values throughout. An Observable may be completed, which means it won’t emit any further values. An Observer subscribes to these Observables. These Observers are essentially callbacks to emissions of the Observable. This paradigm supports asynchronous operations naturally. In our application, the Angular 2 components have functions which act as Observers, while the data-sharing service can act as an Observable.
Defining Data Sources with Subjects
But since the data-sharing service is not the actual source of the data, Observables are not enough. Our data-sharing service would need to observe the data source (in our case, some HTTP module) while emitting the fetched data. Hence, we need Subjects. A Subject is both an observer and an observable. This is how it works:
class SharingService {
private data1= new Subject();
getData1():() => Observable {
return this.data1.asObservable();
}
refresh() {
Net.fetch().then(data => {
this.data1.next(data);
});
}
}
//In Component CustomComp
ngOnInit() {
this.sharingService.getData1().subscribe(d => {
if(goog.isDefAndNotNull(d)){
this.data1 = d;
}
});
}
This sharing service has a Subject. We only need the Observable portion of the subject for our components: The asObservable
method is used to get the data. We also have another method called refresh
. This method uses the Net module to fetch the data from the back-end service and pipes it into the Subject using the next
call, to which it reacts by emitting the same value.
Storing the Last Value with BehaviorSubject
The data reaches the component when refresh
is called on the sharing service and when the component subscribes using the method getData1
. However, this solution still isn’t quite right. A normal Subject will emit only future events to an Observer after subscription. For example, if component B subscribes to the data after it is refreshed once, it might not get any data at all unless it’s refreshed again or it subscribed before data was fetched by the Net module. But there is an easier solution to this problem.
BehaviorSubject solves our last problem; it is a type of Subject which always emits the last emitted value to any new subscriber. Unfortunately, BehaviorSubject needs an initial value. Since our data source is a back-end service, there is no synchronous value to initialize with. Hence, we live with using an undefined
as the initial state. The key benefit in this approach is that when component B initiates a refresh, component A will automatically receive the new data. Component A doesn’t need any kind of messaging system to be informed about new data. The data in component A is ever-changing throughout its lifetime.
Dealing with Update Propagation
There is still one more problem left to be addressed. Since none of the components talk to each other, it’s pretty hard to know when a refresh needs to happen. There is no reason to fetch the data before it’s actually necessary. At the same time, each component shouldn’t need to refresh again and again unless it’s necessary. The simplest solution might be to add a flag to the SharingService
, to indicate the availability of data. However, this solution requires that the components know about the internals of our SharingService
. A better approach might be to expose a different API to the components that takes care of handling the refresh
internally. Here is what it looks like:
class SharingService {
private data1 = new BehaviorSubject(undefined);
private fetching: boolean;
private getData1() {
return this.data1.asObservable();
}
awaitData() {
if(!goog.isDef(this.data1.getValue()) && !this.fetching){
this.refresh();
}
return this.getData1();
}
refresh() {
this.fetching = true;
Net.fetch().then(data => {
this.fetching = false;
this.data1.next(data);
},err => {
this.fetching = false;
this.data1.error(err);
});
}
}
//In CustomCompB
ngOnInit() {
this.updateData(); // Function that changes data1
this.sharingService.refresh();
}
Several improvements have been made in the above snippet. The awaitData
method is a better solution to the last problem—it decides whether or not it is necessary to fetch new data while returning an Observable of the data source. We also added error handling to the refresh
method. The below sequence diagram helps visualize the interactions between all the pieces from our final example:

How Can You Benefit From Observables
To summarize, using the Observable pattern provides the following key benefits while developing complex web applications:
- Provides an easy-to-use event-like abstraction layer where Observable emissions are synonymous with events
- Helps develop asynchronous, user-interactive applications efficiently
- Enables a simple communication mechanism across different parts of the application without introducing explicit dependencies between components
Thank you for the article, overall is a great article, but I would rewrite this sentence:
“An Observable, in the simplest of terms, is just like a Promise but better.”
Both are not related and give such affirmation can lead to confusion for reader that are not already familiar with observables.
typo: getData1() should return this.data1.asObservable(); instead of this.data.asObservable();
Thanks for pointing it out! I fixed it!
Thanks for the suggestion. I rephrased it to be better.
We found that you can have a “cold” BehaviorSubject if you create a ReplaySubject with a buffer of 1. Then it doesn’t require an initial value.
That might actually solve the initial-state issue. Thanks a lot Brian!
To use Subjects and BehaviorSubject which angular module i need to import ?
BehaviorSubject and Subject can be imported from rxjs module like:
import {BehaviorSubject} from 'rxjs/BehaviorSubject';
Great article Sriraam. I’m struggling to make this work within a polling scenario where current data is immediately available to subscribers but will be refreshed every 30 seconds. Any pointers?
Thank you for the article!!
Where can I get the NET module? Is it a requirement or how can I replace it?
Great article!
It’s a very nice piece of code, very well explained.
Thank you for share your knowledge.
it helped me! Thanks!!)
@mchid
I am not sure if I understand your concern. But from your description, if current data is a number,
In your setup, what is the `this.data1.error(err);` suppose to do?
I have a similar setup, but have had to refactor quite a bit as calling BehaviorSubject.error() cancels all subscriptions to that subject. So no further communication is possible between that subject and any views that may be subscribed. I was attempting a retry of the Http connection, and then calling BehaviorSubject.next(result) – which was how I spotted the issue.
Wondering what your process is for handling this?
`this.data1.error(err)` was meant to do exactly what you described. But in my application, I assume that any response other than a 200/404 is terrible(even once) and I close the subscription, indirectly communicating with the views. This works for my application.
The process to handle this should be based on what your application needs.
1) If your views don’t care about retries, you can choose to retry with back-off for n-times. However, if you failed after n-times, it means the user has probably gone offline or if the server is facing issues. If your application can afford a refresh for any of these scenarios, I say, we do `this.data1.error(err)` and severe all subscriptions.
2) If your views care about retry or if your application cannot afford a refresh, the best thing would be to use a complex messaging format for communication between the data-service and the subscribed views. `this.data1.error(err)` option may not work for you here.
Hope this answers your question!
Tnx a lot. I do have 2 question:
1. In case the data (examples: team, user) is switched, is it necessary to unsubscribe and subscribe again ?
2. When do you call await function ?
I am not sure I understand, but the type of data is determined at the time we create the BehaviorSubject. When you subscribe to this behaviorSubject, the data received would be of this type. You can only publish data of that type to that BehaviorSubject. So, switching data cannot happen dynamically, unless you use Generics.
The
awaitData
function returns an observable. So, using that would be likeawaitData().subscribe ( d =>
thanks for the article – I do have a question for a problem that I came across. What if component A and B are running at the same time but need to show different values for `data` (for example I might want to be showing a “developer” record (from the user table) and a “manager” record in a master-detail view ?