RxJS is a JavaScript implementation of a ReactiveX library that allows working with asynchronous, event-based code. ReactiveX (so as RxJS) is based on an observer pattern.
Pull vs. Push
RxJs is a push-based library, so it is important to understand the differences between push and pull systems for a complete understanding. The whole difference is about how data Producer & data Consumer interact.
Pull-based behavior
Every JavaScript Function is a Pull system. When working with functions, first the call occurs, and then we receive some data from it. The code that calls the function is a Consumer, and the function is a data Producer.
In this case, the Consumer pulls data from the Producer, and it’s a Consumer who determines when it receives the data. The Consumer is an active element, while the Producer remains dormant until the data is pulled from it.
Push-based behavior
The most common example of a push system is Promise. Promise (data Producer) delivers resolved value to the registered callback functions (data Consumers). Promise determines when the callback function will receive the data - in other words, it’s responsible for pushing the data. The callback function is inactive until it receives the data. An interaction between the RxJS Observable (Producer) and Observer (Consumer) is based on the same behavior.
Single- and multiple-value Producers
In a Pull system, a single-value Producer is a JavaScript function since it can only return a single value on invocation. In a Push system, a single-value Producer is represented by Promise: the resolved-value could only be sent once.
Multiple-value Producers are distinguished by their ability to return n-values over time, and they’re present in both systems. In a Pull system, a multiple-value Producer is a JavaScript Generator function. Unlike simple functions, a Generator function is capable of returning zero to (potentially) infinite values on iteration. But it is still controlled by Consumer since the generator method next() is called from the ‘external’ code, leading to resuming a Generator function execution.
In the scope of a Push system, an example of a multiple-value Producer might be an Event Listener. User interaction with a ‘targeted’ DOM element leads to multiple calls of the callback function. But in contrast to the Generator function, data Consumer (callback function) remains passive while data Producer (events triggered by user’s interaction) decides when Consumer will receive data.
To summarise: in Pull systems, the Consumer determines when it receives data from the data Producer, while in Push systems, the Producer determines when to send data to the Consumer.
Basic RxJS concepts
What is Stream?
A Stream is a sequence of values in some time interval. It could be a simple numbers sequence generated within 6 seconds, the events from various elements on a form, text messages in chat sent via WebSocket. Everything above is the examples of data, which will be collected over some timespan.RxJS Observables are made to make work with Streams much more convenient.
Observable
Observables are objects that can emit data over some time. You can think of it as a function that returns the data Stream to the Observers, synchronously or asynchronously. The number of data returned by a Stream varies from zero to infinite.
Observers & Subscriptions
For Observable to work, it needs the Observer and a subscription. An Observer is a consumer of values delivered by an Observable.
The Subscription serves as a link between the Observable and the Observer: the Observer connects to the Observable via the subscribe() method.
Observable lifecycle
While working with Observers & subscriptions, the Observable goes through four stages of its lifecycle:
- Creation
- Subscription
- Execution
- Disposal
Creating Observable
Be default the Observable is created in the following way:
The Observable constructor requires only one argument: a subscriber function.
When we mention the Observable execution, we talk about the execution of a code inside the subscriber function. To invoke the Observable we need the Observer and a subscription.
We subscribe to the Observable by using the subscribe() method: Now that the Observable has gained a subscriber, the following result will be printed in the console: ‘Hello World!’.
Note that every subscription leads to the separate execution of the Observable. Multiple Observers that subscribed to the same Observable aren’t connected in any way.
Observable produces n-values (in sync or async manner) that get delivered to a subscriber during the execution. There are three types of values an Observable Execution can deliver:
- "Next" notification: sends a value such as a Number, a String, an Object, etc. The number of “next” notifications isn’t limited.
- "Error" notification: sends a JavaScript Error or exception.
- "Complete" notification: does not send a value, serves as a signal that the Observable has completed successfully. You cannot pass values with that notification!
The "completed" and "error" states are final. That means, Observables cannot emit any data after delivering these notifications.
Giving the example:
The console output would be:
“I am number 1”
“I am number 2”
“I am number 3” won’t be printed since the callback inside the subscribe method won’t send the data with the “complete” notification. And since the Observable won’t deliver anything after success/error notifications, the “I am number 4” also won’t be printed.
Cold vs. Hot Observables
In a nutshell:
- If the data is generated by Observable itself, we call it a ‘cold’ Observable;
- If the data is generated outside the Observable, we call it ‘hot’.
Now, with more details:
Cold Observables
By default, Observables are lazy - the Observable execution only happens for each Observer that subscribes. The Observable starts a new execution for every subscriber, no data is shared between them.
If the Observable generates multiple values, the situation when two subscribers receive different values can easily occur:
In this case, the data generated inside the Observable (Math.random()), which means we’re dealing with the cold Observable. The subscribers are getting different values since the execution with the random number generation starts separately for every subscriber.
Hot Observables
In order to transform cold Observable into a hot one, we just need to move the data generation outside the Observable. Let’s slightly tweak a previous example:
Now it’s a hot Observable since the data generation (Math.random()) occurs no matter if the Observable has the subscriber or not. The only difference is that by default, that data will be simply lost without the subscribers.
So, what’s better?
Depends on the use case. In general, you would want to keep the Observables cold, unless:
- You need the ability to have n-subscribers that will receive the same data
- You’re dealing with the new instance. For example, a WebSocket connection: you probably don’t want to create a separate connection for every new subscriber. Instead, you’d want to share a single connection between all of them.
Operators
Operators are what make RxJS useful. They allow you to manipulate the data from its source, returning an Observable with the modified data.
There’s a ton of useful operators provided by RxJS out of the box, one for almost every use case.
How to use Operators
Most operators are known as Pipeable Operators since you can chain them with the pipe() method. Pipeable Operator is a pure function that takes one Observable as input and generates another Observable as output. They are located at ‘rxjs/operators’.
A pipe() method accepts Operators as params, and then calls then one by one:
Every subsequent Operator will receive a new Observable that was generated as a result of previous Operator execution.
Categories of Operators
RxJS Operators are split into categories which makes the search process much easier:
Combination Operators
They Allow combining the data from the multiple Observables. For instance, we can combine data updates from different sources to perform some calculations. Operator combineLatest() produces last values from each Observable whenever one of these produces a value:
Creation Operators
These Operators can produce the Observable almost out of anything. For example, you can create a progress bar that will indicate the progress of reading the article. You can use the fromEvent() Operator to achieve that goal. It allows to create of an Observable based on the event data:
Error Handling Operators
This group allows us to handle errors efficiently and repeat the query if needed. The most used operator is catchError (you have to return the Observable when using it!):
Filtering Operators
Operators from this group are responsible for data stream filtering, and they also help when you need to adjust accumulated values in the data stream. For example, the take operator generates only the specified number of values until it completes its execution. You can subscribe to a click-event and process only the first click:
Transformation Operators
These Operators provide us with the method to transform the data. Like the scan operator that accumulates the state in a way like it works in Redux:
Subjects
Another big concept in RxJS.
Subject is a special type of Observable. It allows values to be multicasted to many Observers, which can subscribe to the Subject even when its execution has already started (on the contrary, the execution of a default Observable is unique for every subscriber). Let’s take a look at the example:
The following result will be printed in the console:
1st: 3
1st: 9
2nd: 9
A Subject is created using the new Subject() constructor.
Same as with the Observable, Observers are subscribed to it via the subscribe() method, which receives from Subject values of three types: next, error, and complete.
Internally to the Subject, subscribe() method does not invoke a new execution that delivers values. It simply registers the given Observer in a list of Observers.
Aside from the “standard” Subject, there are three varieties of it:
BehaviorSubject
BehaviorSubject stores the last emitted value. So every newly subscribed Observer will receive the “current value” immediately.
The initial value is set during the BehaviorSubject initialisation:
The following result will be printed in the console:
1st: 5
2nd: 5 - if it was a “default” Subject, a second subscriber wouldn’t receive the last value, only the next one
1st: 7
2nd: 7
ReplaySubject
Unlike BehaviorSubject, a ReplaySubject object can store a given amount of the lastly emitted values. That amount is set during the object creation process.
Every Observer will receive the n-amount of “replayed” values of a ReplaySubject:
The following result will be printed in the console:
1st: 5
1st: 6
1st: 7
2nd: 6
2nd: 7
AsyncSubject
With the AsyncSubject the Observers will receive only the last emitted value and only when it will complete its execution:
In 3 seconds, the following result will be printed in the console:
Async: 9
Observable vs. Promise
Here are the key differences between Promise and Observable:
Callback-function execution
Callback-function that we send to a Promise constructor gets executed immediately:
The console output:
‘Callback call’
‘Before calling then…’
"Greeting from Promise: A-a-and resolved!"
In the case of cold Observable, a callback function will be executed only after calling a subscribe() method:
The following result will be printed in the console:
“Before calling subscribe…”
“Callback call”
“Next!”
“Complete the Observable”
Asynchrony
Promise is always asynchronous, even if it resolves instantly:
The console output:
"Before calling then…"
"After calling then..."
"Greeting from Promise: Promise Resolved!"
The message from then() method will be the last one displayed even though Promise was resolved without delay.
On the other hand, the Observable can be synchronous:
Console output:
"Before calling subscribe..."
"Callback call"
"Next!"
"Complete the Observable"
"After calling subscribe…"
And it can be asynchronous:
Console output:
"Before calling subscribe..."
"Callback call"
"After calling subscribe…"
"Next!"
"Complete the Observable"
Observables are collections of multiple values
A Promise can return only one value. It could be an array, but it’s a single object still. An Observable can emit n-values during its execution.
Operators
The aforementioned RxJS feature allows users to manipulate and modify the Observable data stream. Promise objects don’t have anything like that.
Memory Leak & Unsubscribe
Many Observables that we are subscribing to are potentially infinite (such as click-events) - we can’t tell beforehand how many values will be emitted. This means that we have to manipulate our Observable subscription on our own.
Why does memory leak occur?
Let’s take a look at the Angular component for reference:
In this example, we’re using the RxJS function - timer. It creates an Observable that emits numbers. In our case - every second.
The problem is, after the component will be unmounted, our subscription will live on, and it’ll keep printing the outputs in the console. And if the app reinitializes the same component, we’ll end up having another subscription, and so on and so on. An example of the memory leak is the components recreation without cleaning up our subscriptions.
How to manage the subscriptions to avoid memory leak
The unsubscribe() method:
An Observable subscribe() method returns a Subscription object. It has a method called unsubscribe(), which used to remove the subscription:
A takeUntil() Operator:
The takeUntil() Operator allows us to use a declarative approach while working with data streams:
A takeUntil(notifier: Observable<any>) Operator emits values produced by Observable until a notifier Observable produces values.
This is a declarative approach since we’re declaring our Observable-chain along with everything it needs for a complete lifecycle.
A take() and first() Operators:
Sometimes we need a subscription to work only once - for instance, when we’re loading some profile data at the application initialization.
In this case, it’s better to use either first() Operator (which emits only the first value), either take() (which emits only n-values), both unsubscribe automatically:
In this example, the console output will display 0, 1, and 2 values. Afterward, the take() Operator will unsubscribe from the Observable.
Notice: take(1) and first() Operators won’t unsubscribe from the Observable before the first value is emitted. Use them only when you’re certain that at least one value will be produced. Otherwise, the subscription will remain active, which may lead to unexpected behavior.
Afterword
This article is meant to give you an overview of what RxJS is about without going too deep into each concept. Hopefully, it’ll add more clarity on the subject, but I definitely recommend taking a look at the official RxJS documentation for a deep dive into the topic.
Retool Dashboards with HubSpot Integration
Our client needed a centralized tool to aggregate account and contact activity, improving visibility and decision-making for the sales team.
The solution
We built a Retool application integrated with HubSpot, QuickMail, and Clay.com. The app features dashboards for sorting, filtering, and detailed views of companies, contacts, and deals, along with real-time notifications and bidirectional data syncing.
The result
- MVP in 50 hours: Delivered a functional application in just 50 hours.
- Smarter decisions: Enabled data-driven insights for strategic planning.
- Streamlined operations: Reduced manual tasks with automation and real-time updates.
Lead Generation Tool to Reduce Manual Work
Our client, Afore Capital, a venture capital firm focused on pre-seed investments, aimed to automate their lead generation processes but struggled with existing out-of-the-box solutions. To tackle this challenge, they sought assistance from our team of Akveo Retool experts.
The scope of work
The client needed a tailored solution to log and track inbound deals effectively. They required an application that could facilitate the addition, viewing, and editing of company and founder information, ensuring data integrity and preventing duplicates. Additionally, Afore Capital aimed to integrate external tools like PhantomBuster and LinkedIn to streamline data collection.
The result
By developing a custom Retool application, we streamlined the lead generation process, significantly reducing manual data entry. The application enabled employees to manage inbound deals efficiently while automated workflows for email parsing, notifications, and dynamic reporting enhanced operational efficiency. This allowed Afore Capital's team to focus more on building relationships with potential founders rather than on administrative tasks.
Learn more about the case
Retool CMS Application for EdTech Startup
Our client, CutTime, a leading fine arts education management platform, needed a scalable CMS application to improve vendor product management and user experience.
The scope of work
We developed a Retool application that allows vendors to easily upload and manage product listings, handle inventory, and set shipping options. The challenge was to integrate the app with the client’s system, enabling smooth authentication and product management for program directors.
The result
Our solution streamlined product management, reducing manual work for vendors, and significantly improving operational efficiency.
Building Reconciliation Tool for e-commerce company
Our client was in need of streamlining and simplifying its monthly accounting reconciliation process – preferably automatically. But with a lack of time and low budget for a custom build, development of a comprehensive software wasn’t in the picture. After going through the case and customer’s needs, we decided to implement Retool. And that was the right choice.
The scope of work
Our team developed a custom reconciliation tool designed specifically for the needs of high-volume transaction environments. It automated the processes and provided a comprehensive dashboard for monitoring discrepancies and anomalies in real-time.
The implementation of Retool significantly reduced manual effort, as well as fostered a more efficient and time-saving reconciliation process.
Creating Retool Mobile App for a Wine Seller
A leading spirits and wine seller in Europe required the development of an internal mobile app for private client managers and administrators. The project was supposed to be done in 1,5 months. Considering urgency and the scope of work, our developers decided to use Retool for swift and effective development.
The scope of work
Our developers built a mobile application tailored to the needs of the company's sales force: with a comprehensive overview of client interactions, facilitated order processing, and enabled access to sales history and performance metrics. It was user-friendly, with real-time updates, seamlessly integrated with existing customer databases.
The result? Increase in productivity of the sales team and improved decision-making process. But most importantly, positive feedback from the customers themselves.
Developing PoC with Low Code for a Tour Operator
To efficiently gather, centralize, and manage data is a challenge for any tour operator. Our client was not an exception. The company was seeking to get an internal software that will source information from third-party APIs and automate the travel itinerary creation process. Preferably, cost- and user-friendly tool.
The scope of work
Our experts ensured the client that all the requirements could be covered by Retool. And just in 40 hours a new software was launched. The tool had a flexible and easy-to-use interface with user authentication and an access management system panel – all the company needed. At the end, Retool was considered the main tool to replace the existing system.
Testing New Generation of Lead Management Tool with Retool
Our client, a venture fund, had challenges with managing lead generation and client acquisition. As the company grew, it aimed to attract more clients and scale faster, as well as automate the processes to save time, improve efficiency and minimize human error. The idea was to craft an internal lead generation tool that will cover all the needs. We’ve agreed that Retool will be a perfect tool for this.
The scope of work
The project initially began as a proof of concept, but soon enough, with each new feature delivered, the company experienced increased engagement and value.
We developed a web tool that integrates seamlessly with Phantombuster for data extraction and LinkedIn for social outreach. Now, the company has a platform that elevates the efficiency of their lead generation activities and provides deep insights into potential client bases.
Building an Advanced Admin Portal for Streamlined Operations
Confronted with the need for more sophisticated internal tools, an owner of IP Licensing marketplace turned to Retool to utilize its administrative functions. The primary goal was to construct an advanced admin portal that could support complex, multi-layered processes efficiently.
The scope of work
Our client needed help with updating filters and tables for its internal platform. In just 30 hours we've been able to update and create about 6 pages. Following features were introduced: add complex filtering and search, delete records, styling application with custom CSS.
Together, we have increased performance on most heavy pages and fixed circular dependency issues.
Creating MVP Dashboard for Google Cloud Users
Facing the challenge of unoptimized cloud resource management, a technology firm working with Google Cloud users was looking for a solution to make its operations more efficient. The main idea of the project was to create an MVP for e-commerce shops to test some client hypotheses. Traditional cloud management tools fell short.
The scope of work
Determined to break through limitations, our team of developers turned Retool. We decided to craft an MVP Dashboard specifically for Google Cloud users. This wasn't just about bringing data into view; but about reshaping how teams interact with their cloud environment.
We designed a dashboard that turned complex cloud data into a clear, strategic asset thanks to comprehensive analytics, tailored metrics, and an intuitive interface, that Retool provides. As the results, an increase in operational efficiency, significant improvement in cost management and resource optimization.
Elevating CRM with Custom HubSpot Sales Dashboard
Our other client, a SaaS startup, that offers collaborative tools for design and engineering teams, was on a quest to supercharge their sales efforts. Traditional CRM systems were limited and not customizable enough. The company sought a solution that could tailor HubSpot to their workflow and analytics needs.
The scope of work
Charged with the task of going beyond standard CRM functions, our team turned to Retool. We wanted to redefine how sales teams interact with their CRM.
By integrating advanced analytics, custom metrics, and a user-friendly interface, our developers provided a solution that transformed data into a strategic asset.
In 40 hours, three informative dashboards were developed, containing the most sensitive data related to sales activities. These dashboards enable our customer to analyze sales and lead generation performance from a different perspective and establish the appropriate KPIs.
Building a PDF Editor with Low-Code
Our client, a leading digital credential IT startup, needed a lot of internal processes to be optimized. But the experience with low-code tools wasn’t sufficient. That’s why the company decided to hire professionals. And our team of developers joined the project.
The scope of work
The client has a program that designs and prints custom badges for customers. The badges need to be “mail-merged” with a person’s info and turned into a PDF to print. But what is the best way to do it?
Our developers decided to use Retool as a core tool. Using custom components and JavaScript, we developed a program that reduced employees' time for designing, putting the data, verifying, and printing PDF badges in one application.
As a result, the new approach significantly reduces the time required by the internal team to organize all the necessary staff for the conference, including badge creation.