Skip to content

Going Reactive with RxJS

RxJS is the perfect tool for implementing reactive programming paradigms to your software development. In general, software development handling errors gracefully is a fundamental piece of ensuring the integrity of the application as well as ensuring the best possible user experience.

In this article, we will look at how we handle errors with RxJS and then look at how we can use RxJS to build a simple yet performant application.

Handling Errors

Our general approach to errors usually consists of us exclaiming "Oh no! What went wrong?" but it's something that is a common occurrence in all applications. The ability to manage errors well without disrupting the user's experience, while also providing accurate error logs to allow a full diagnosis of the cause of the error, is more important than ever.

RxJS gives us the tools to do this job very well! Let's take a look at some basic error handling approaches with RxJS.

Basic Error Handling

The most basic way of detecting and reacting to an error that has occurred in an Observable stream provided to us by the .subscribe() method.

obs$.subscribe(
    value => console.log("Received Value: ", value),
    error => console.error("Received Error: ", error)
)

Here we can set up two different pieces of logic—one to handle non-error emission from the Observable and one to gracefully handle errors emitted by the Observable.

We could use this to show a Notification Toast or Alert to inform the user that an error has occurred:

obs$.subscribe(
    value => console.log("Received Value: ", value),
    error => showErrorAlert(error)
)

This can help us minimize disruption for the user, giving them instant feedback that something actually hasn't worked as appropriate rather than leaving them to guess.

Composing Useful Errors

Sometimes, however, we may have situations wherein we want to throw an error ourselves. For example, some data we received isn't quite correct, or maybe some validation checks failed.

RxJS provides us with an operator that allows us to do just that. Let's take an example where we are receiving values from an API, but we encounter missing data that will cause other aspects of the app not to function correctly.

obs$
  .pipe(
    mergeMap((value) =>
      !value.id ? throwError("Data does not have an ID") : of(value)
    )
  )
  .subscribe(
    (value) => console.log(value),
    (error) => console.error("error", error)
  );

If we receive a value from the Observable that doesn't contain an ID, we throw an error that we can handle gracefully.

NOTE: Using the throwError will stop any further Observable emissions from being received.

Advanced Error Handling

We've learned that we can handle errors reactively to prevent too much disruption for the user.

But what if we want to do multiple things when we receive an error or even do a retry?

RxJS makes it super simple for us to retry errored Observables with their retry() operator.

Therefore, to create an even cleaner error handling setup in RxJS, we can set up an error management solution that will receive any errors from the Observable, retry them in the hopes of a successful emission, and, failing that, handle the error gracefully.

obs$
  .pipe(
    mergeMap((value) =>
      !value.id ? throwError("Data does not have an ID") : of(value)
    ),
    retry(2),
    catchError((error) => {
        // Handle error gracefully here
      console.error("Error: ", error);
      return EMPTY;
    })
  )
  .subscribe(
    (value) => console.log(value),
    () => console.log("completed")
  );

Once we reach an error, emitting the EMPTY observable will complete the Observable. The output of an error emission above is:

Error:  Data does not have an ID
completed 

Usage in Frontend Development

RxJS can be used anywhere running JavaScript; however, I'd suggest that it's most predominately used in Angular codebases. Using RxJS correctly with Angular can massively increase the performance of your application, and also help you to maintain the Container-Presentational Component Pattern.

Let's see a super simple Todo app in Angular to see how we can use RxJS effectively.

Basic Todo App

We will have two components in this app: the AppComponent and the ToDoComponent. Let's take a look at the ToDoComponent first:

import {
  ChangeDetectionStrategy,
  Component,
  EventEmitter,
  Input,
  Output
} from "@angular/core";

export interface Todo {
  id: number;
  title: string;
}

@Component({
  selector: "todo",
  template: `
    <li>
      {{ item.title }} - <button (click)="delete.emit(item.id)">Delete</button>
    </li>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class ToDoComponent {
  @Input() item: Todo;
  @Output() delete = new EventEmitter<number>();
}

Pretty simple, right? It takes an item input and outputs an event when the delete button is clicked. It performs no real logic itself other than rendering the correct HTML.

One thing to note is changeDetection: ChangeDetectionStrategy.OnPush. This tells the Angular Change Detection System that it should only attempt to re-render this component when the Input has changed.

Doing this can increase performance massively in Angular applications and should always be applicable to pure presentational components, as they should only be rendering data.

Now, let's take a look at the AppComponent.

import { Component } from "@angular/core";
import { BehaviorSubject } from "rxjs";
import { Todo } from "./todo.component";

@Component({
  selector: "my-app",
  template: `
    <div>
      <h1>
        ToDo List
      </h1>
      <div style="width: 50%;">
        <ul>
          <todo
            *ngFor="let item of (items$ | async); trackBy: trackById"
            [item]="item"
            (delete)="deleteItem($event)"
          >
          </todo>
        </ul>
        <input #todoTitle placeholder="Add item" /><br />
        <button (click)="addItem(todoTitle.value, todoTitle)">Add</button>
      </div>
    </div>
  `,
  styleUrls: ["./app.component.css"]
})
export class AppComponent {
  private items: Todo[] = [{ id: 1, title: "Learn RxJS" }];
  items$ = new BehaviorSubject<Todo[]>(this.items);

  addItem(title: string, inputEl: HTMLInputElement) {
    const item = {
      id: this.items[this.items.length - 1].id + 1,
      title,
      completed: false
    };
    this.items = [...this.items, item];
    this.items$.next(this.items);

    inputEl.value = "";
  }

  deleteItem(idToRemove: number) {
    this.items = this.items.filter(({ id }) => id !== idToRemove);
    this.items$.next(this.items);
  }

  trackById(index: number, item: Todo) {
    return item.id;
  }
}

This is a container component, and it's called this because it handles the logic relating to updating component state as well as handles or dispatches side effects.

Let's take a look at some areas of interest:

private items: Todo[] = [{ id: 1, title: "Learn RxJS" }];
items$ = new BehaviorSubject<Todo[]>(this.items);

We create a basic local store to store our ToDo items; however, this could be done via a state management system or an API.
We then set up our Observable, which will stream the value of our ToDo list to anyone who subscribes to it.

You may now look over the code and begin to wonder where we have subscribed to items$.

Angular provides a very convenient Pipe that handles this for us. We can see this in the template:

*ngFor="let item of (items$ | async); trackBy: trackById"

In particular, it's the (items$ | async) this will take the latest value emitted from the Observable and provide it to the template. It does much more than this though. It also will manage the subscription for us, meaning when we destroy this container component, it will unsubscribe automatically for us, preventing unexpected outcomes.

Using a pure pipe in Angular also has another performance benefit. It will only ever re-run the code in the Pipe if the input to the pipe changes. In our case, that would mean that item$ would need to change to a whole new Observable for the code in the async pipe to be executed again. We never have to change item$ as our values are then streamed through the Observable.

Conclusion

Hopefully, you have learned both about how to handle errors effectively as well put RxJS into practice into a real-world app that improves the overall performance of your application. You should also start to see the power that using RxJS effectively can bring!

This Dot Labs is a development consultancy that is trusted by top industry companies, including Stripe, Xero, Wikimedia, Docusign, and Twilio. This Dot takes a hands-on approach by providing tailored development strategies to help you approach your most pressing challenges with clarity and confidence. Whether it's bridging the gap between business and technology or modernizing legacy systems, you’ll find a breadth of experience and knowledge you need. Check out how This Dot Labs can empower your tech journey.

You might also like

Bun v1.0 cover image

Bun v1.0

On September 8, 2023, Bun version 1 was released as the first production-ready version of Bun, a fast, all-in-one toolkit for running, building, testing, and debugging JavaScript and TypeScript. Why a new JS runtime You may ask, we already have Node and Deno, so why would we need another javascript runtime, Well yes we had Node for a very long time, but developers face a lot of problems with it, and maybe the first problem is because it’s there for a very long time, it has been changing a lot between different versions and one of the biggest nightmares for JavaScript developers these days is upgrading the node version. Also, Node lacks support for Typescriptt. Zig programming language One of the main reasons that Bun is faster than Node, is the programming language it has been built with which is Zig. Zig is a very fast programming language, even faster than C) (here is some benchmarks), it focuses on performance and memory control. The reasons it’s faster than C is because of the LLVM optimizations it has, and also the way it handles the undefined behavior under the hood Developer Experience Bun delivers a better developer experience than Node on many levels. First, it’s almost fully compatible with Node so you can use Node packages without any issues. Also, you don’t need to worry about JS Common and ES Modules anymore, you can use both in the same file, yup you read that right, for example: `js import { useState } from 'react'; const React = require('react'); ` Also, it has a built-in test framework similar to Jest or Vitest in the project so no need to install a different test framework with different bundler in the same project like Webpack or Vite `js import { describe, expect, test, beforeAll } from "bun:test"; ` Also, it supports JSX out-of-the-box `bash bun index.tsx ` Also, Bun has the fastest javascript package manager and the most efficient you can find as of the time of this post `bash bun install ` Bun Native APIs Bun supports the Node APIs but also they have fun and easy APIs to work with like `Bun.serve()` : to create HTTP server `Bun.file()` : to read and write the file system `Bun. password.hash()`: to hash passwords `Bun.build()`: to bundle files for the browser `Bun.FileSystemRouter()`: a file system router And many more features Plugin system Bun also has an amazing plugin system that allows developers to create their own plugins and add them to the Bun ecosystem. `js import { plugin, type BunPlugin } from "bun"; const myPlugin: BunPlugin = { name: "Custom loader", setup(build) { // implementation }, }; ` Conclusion Bun is a very promising project, and it’s still in the early stages, but it has a lot of potential to be the next big thing in the JavaScript world. It’s fast, easy to use, and has a lot of amazing features. I’m very excited to see what the future holds for Bun and I’m sure it will be a very successful project....

Angular 17: Continuing the Renaissance cover image

Angular 17: Continuing the Renaissance

Angular 17: A New Era November 8th marked a significant milestone in the world of Angular with the release of Angular 17. This wasn't just any ordinary update; it was a leap forward, signifying a new chapter for the popular framework. But what made this release truly stand out was the unveiling of Angular's revamped website, complete with a fresh brand identity and a new logo. This significant transformation represents the evolving nature of Angular, aligning with the modern demands of web development. To commemorate this launch, we also hosted a release afterparty, where we went deep into its new features with Minko Gechev from the Angular core team, and Google Developer Experts (GDEs) Brandon Roberts, Deborah Kurata, and Enea Jahollari. But what exactly are these notable new features in the latest version? Let's dive in and explore. The Angular Renaissance Angular has been undergoing a significant revival, often referred to as Angular's renaissance, a term coined by Sarah Drasner, the Director of Engineering at Google, earlier this year. This revival has been particularly evident in its recent versions. The Angular team has worked hard to introduce many new improvements, focusing on signal-based reactivity, hydration, server-side rendering, standalone components, and migrating to esbuild and Vite for a better and faster developer experience. This latest release, in particular, marks many of these features as production-ready. Standalone Components About a year ago, Angular began a journey toward modernity with the introduction of standalone components. This move significantly enhanced the developer experience, making Angular more contemporary and user-friendly. In Angular's context, a standalone component is a self-sufficient, reusable code unit that combines logic, data, and user interface elements. What sets these components apart is their independence from Angular's NgModule system, meaning they do not rely on it for configuration or dependencies. By setting a standalone: true` flag, you no longer need to embed your component in an NgModule and you can bootstrap directly off that component: `typescript // ./app/app.component.ts @Component({ selector: 'app', template: 'hello', standalone: true }) export class AppComponent {} // ./main.ts import { bootstrapApplication } from '@angular/platform-browser'; import { AppComponent } from './app/app.component'; bootstrapApplication(AppComponent).catch(e => console.error(e)); ` Compared to the NgModules way of adding components, as shown below, you can immediately see how standalone components make things much simpler. `ts // ./app/app.component.ts import { Component } from '@angular/core'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'], }) export class AppComponent { title = 'CodeSandbox'; } // ./app/app.module.ts import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { AppComponent } from './app.component'; @NgModule({ declarations: [ AppComponent ], imports: [ BrowserModule ], providers: [], bootstrap: [AppComponent] }) export class AppModule { } // .main.ts import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { AppModule } from './app/app.module'; platformBrowserDynamic() .bootstrapModule(AppModule) .catch((err) => console.error(err)); ` In this latest release, the Angular CLI now defaults to generating standalone components, directives, and pipes. This default setting underscores the shift towards a standalone-centric development approach in Angular. New Syntax for Enhanced Control Flow Angular 17 introduces a new syntax for control flow, replacing traditional structural directives like ngIf` or `ngFor`, which have been part of Angular since version 2. This new syntax is designed for fine-grained change detection and eventual zone-less operation when Angular completely migrates to signals. It's more streamlined and performance-efficient, making handling conditional or list content in templates easier. The @if` block replaces `*ngIf` for expressing conditional parts of the UI. `ts @if (a > b) { {{a}} is greater than {{b}} } @else if (b > a) { {{a}} is less than {{b}} } @else { {{a}} is equal to {{b}} } ` The @switch` block replaces `ngSwitch`, offering benefits such as not requiring a container element to hold the condition expression or each conditional template. It also supports template type-checking, including type narrowing within each branch. ```ts @switch (condition) { @case (caseA) { Case A. } @case (caseB) { Case B. } @default { Default case. } } ``` The @for` block replaces `*ngFor` for iteration and presents several differences compared to its structural directive predecessor, `ngFor`. For example, the tracking expression (calculating keys corresponding to object identities) is mandatory but offers better ergonomics. Additionally, it supports `@empty` blocks. `ts @for (item of items; track item.id) { {{ item.name }} } ` Defer Block for Lazy Loading Angular 17 introduces the @defer` block, a dramatically improving lazy loading of content within Angular applications. Within the `@defer` block framework, several sub-blocks are designed to elegantly manage different phases of the deferred loading process. The main content within the `@defer` block is the segment designated for lazy loading. Initially, this content is not rendered, becoming visible only when specific triggers are activated or conditions are met, and after the required dependencies have been loaded. By default, the trigger for a `@defer` block is the browser reaching an idle state. For instance, take the following block: it delays the loading of the calendar-imp` component until it comes into the viewport. Until that happens, a placeholder is shown. This placeholder displays a loading message when the `calendar-imp` component begins to load, and an error message if, for some reason, the component fails to load. `ts @defer (on viewport) { } @placeholder { Calendar placeholder } @loading { Loading calendar } @error { Error loading calendar } ` The on` keyword supports a wide a variety of other conditions, such as: - idle` (when the browser has reached an idle state) - interaction` (when the user interacts with a specified element) - hover` (when the mouse has hovered over a trigger area) - timer(x)` (triggers after a specified duration) - immediate` (triggers the deferred load immediately) The second option of configuring when deferring happens is by using the when` keyword. For example: `ts @defer (when isVisible) { } ` Server-Side Rendering (SSR) Angular 17 has made server-side rendering (SSR) much more straightforward. Now, a --ssr` option is included in the `ng new` command, removing the need for additional setup or configurations. When creating a new project with the `ng new` command, the CLI inquires if SSR should be enabled. As of version 17, the default response is set to 'No'. However, for version 18 and beyond, the plan is to enable SSR by default in newly generated applications. If you prefer to start with SSR right away, you can do so by initializing your project with the `--ssr` flag: `shell ng new --ssr ` For adding SSR to an already existing project, utilize the ng add` command of the Angular CLI: `shell ng add @angular/ssr ` Hydration In Angular 17, the process of hydration, which is essential for reviving a server-side rendered application on the client-side, has reached a stable, production-ready status. Hydration involves reusing the DOM structures rendered on the server, preserving the application's state, and transferring data retrieved from the server, among other crucial tasks. This functionality is automatically activated when server-side rendering (SSR) is used. It offers a more efficient approach than the previous method, where the server-rendered tree was completely replaced, often causing visible UI flickers. Such re-rendering can adversely affect Core Web Vitals, including Largest Contentful Paint (LCP), leading to layout shifts. By enabling hydration, Angular 17 allows for the reuse of the existing DOM, effectively preventing these flickers. Support for View Transitions The new View Transitions API, supported by some browsers, is now integrated into the Angular router. This feature, which must be activated using the withViewTransitions` function, allows for CSS-based animations during route transitions, adding a layer of visual appeal to applications. To use it, first you need to import withViewTransitions`: `ts import { provideRouter, withViewTransitions } from '@angular/router'; ` Then, you need to add it to the provideRouter` configuration: `ts bootstrapApplication(AppComponent, { providers: [ provideRouter(routes, withViewTransitions()) ] }) ` Other Notable Changes - Angular 17 has stabilized signals, initially introduced in Angular 16, providing a new method for state management in Angular apps. - Angular 17 no longer supports Node 16. The minimal Node version required is now 18.13. - TypeScript version 5.2 is the least supported version starting from this release of Angular. - The @Component` decorator now supports a `styleUrl` attribute. This allows for specifying a single stylesheet path as a string, simplifying the process of linking a component to a specific style sheet. Previously, even for a single stylesheet, an array was required under `styleUrls`. Conclusion With the launch of Angular 17, the Angular Renaissance is now in full swing. This release has garnered such positive feedback that developers are showing renewed interest in the framework and are looking forward to leveraging it in upcoming projects. However, it's important to note that it might take some time for IDEs to adapt to the new templating syntax fully. While this transition is underway, rest assured that you can still write perfectly valid code using the old templating syntax, as all the changes in Angular 17 are backward compatible. Looking ahead, the future of Angular appears brighter than ever, and we can't wait to see what the next release has in store!...

NgRx Facade Pattern cover image

NgRx Facade Pattern

NgRx Facade Pattern The NgRx Facade Pattern was first introduced by Thomas Burleson in 2018 and has drawn a lot of attention in recent years. In this article, we will discuss the pattern, how to implement it in Angular and discuss whether or not we should_ implement it. What is NgRx? First, what is NgRx? NgRx is a state management solution for Angular built on top of RxJS which adheres to the redux pattern. It contains an immutable centralized store where the state of our application gets stored. - We select slices of state from the Store` using `Selectors`, which we can then render in our components. - We dispatch Actions` to our `Store`. - Our Store` redirects our `Action` to our `Reducers` to recalculate our state and replaces the state within our `Store`. See the diagram below for an illustrated example: This provides us with a tried and tested pattern for managing the state of our application. What is the Facade Pattern? Now that we know what NgRx is, what is the Facade Pattern? Well, what are_ Facades? Facades are a pattern that provides a simple public interface to mask more complex usage. As we use NgRx more and more in our application, we add more actions and more selectors that our components must use and track. This increases the coupling between our component and the actions and selectors themselves. The Facade pattern wants to simplify this approach by wrapping the NgRx interactions in one place, allowing the Component to only ever interact with the Facade. This means you are free to refactor the NgRx artefacts without worrying about breaking your Components. In Angular, NgRx Facades are simply services. They inject the NgRx Store allowing you to contain your interactions with the Store in the service. How do we implement it? To begin with, let's show a Component that uses NgRx directly: `ts export class TodoListComponent implements OnInit { todoList$: Observable; constructor(private store: Store) {} ngOnInit() { this.todoList$ = this.store.select(getLoadedTodoList); this.loadTodoList(); } loadTodoList() { this.store.dispatch(new LoadTodoList()); } addNewTodo(todo: string) { this.store.dispatch(new AddTodo(todo)); } editTodo(id: string, todo: string) { this.store.dispatch(new EditTodo({ id, todo })); } deleteTodo(id: string) { this.store.dispatch(new DeleteTodo(id)); } } ` As we can see, this depends a lot on interactions with the Store and has made our component fairly complex and coupled to NgRx. Let's create a Facade that will encapsulate this interaction with NgRx: `ts @Injectable({ providedIn: 'root', }) export class TodoListFacade { todoList$ = this.store.select(getLoadedTodoList); constructor(private store: Store) {} loadTodoList() { this.store.dispatch(new LoadTodoList()); } addNewTodo(todo: string) { this.store.dispatch(new AddTodo(todo)); } editTodo(id: string, todo: string) { this.store.dispatch(new EditTodo({ id, todo })); } deleteTodo(id: string) { this.store.dispatch(new DeleteTodo(id)); } } ` It's essentially everything we had in the component, except now in a service. We then inject this service into our Component: `ts export class TodoListComponent implements OnInit { todoList$: Observable; constructor(private todoListFacade: TodoListFacade) {} ngOnInit() { this.todoList$ = this.todoListFacade.todoList$; this.todoListFacade.loadTodoList(); } addNewTodo(todo: string) { this.todoListFacade.addNewTodo(todo); } editTodo(id: string, todo: string) { this.todoListFacade.editTodo({ id, todo })); } deleteTodo(id: string) { this.todoListFacade.deleteTodo(id); } } ` By implementing the Facade and using it in our Component, our component no longer depends on NgRx and we do not have to import all actions and selectors. The Facade hides those implementation details, keeping our Component cleaner and easier tested. Pros What are some advantages of using Facades? - It adds a single abstraction of a section of the Store.** - This service can be used by any component that needs to interact with this section of the store. For example, if another component needs to access the TodoListState` from our example above, they do not have to reimplement the action dispatch or state selector code. It's all readily available in the Facade. - Facades are scalable** - As Facades are just services, we can compose them within other Facades allowing us to maintain the encapsulation and hide complex logic that interacts directly with NgRx, leaving us with an API that our developers can consume. Cons - Facades lead to reusing Actions.** - Mike Ryan gave a talk at ng-conf 2018 on Good Action Hygiene which promotes creating as many actions as possible that dictate how your user is using your app and allowing NgRx to update the state of the application from your user's interactions. - Facades force actions to be reused. This becomes a problem as we no longer update state based on the user's interactions. Instead, we create a coupling between our actions and various component areas within our application. - Therefore, by changing one action and one accompanying reducer, we could be impacting a significant portion of our application. - We lose indirection** - Indirection is when a portion of our app is responsible for certain logic, and the other pieces of our app (the view layer etc.) communicate with it via messages. - In NgRx, this means that our Effects or Reducers do not know what told them to work; they just know they have to. - With Facades, we hide this indirection as only the service knows about how the state is being updated. - Knowledge Cost** - It becomes more difficult for junior developers to understand how to interact, update and work with NgRx if their only interactions with the state management solution are through Facades. - It also becomes more difficult for them to write new Actions, Reducers and Selectors as they may not have been exposed to them before. Conclusion Hopefully, this gives you an introduction to NgRx Facades and the pros and cons of using them. This should help you evaluate whether to use them or not....

Being a CTO at Any Level: A Discussion with Kathy Keating, Co-Founder of CTO Levels cover image

Being a CTO at Any Level: A Discussion with Kathy Keating, Co-Founder of CTO Levels

In this episode of the engineering leadership series, Kathy Keating, co-founder of CTO Levels and CTO Advisor, shares her insights on the role of a CTO and the challenges they face. She begins by discussing her own journey as a technologist and her experience in technology leadership roles, including founding companies and having a recent exit. According to Kathy, the primary responsibility of a CTO is to deliver the technology that aligns with the company's business needs. However, she highlights a concerning statistic that 50% of CTOs have a tenure of less than two years, often due to a lack of understanding and mismatched expectations. She emphasizes the importance of building trust quickly in order to succeed in this role. One of the main challenges CTOs face is transitioning from being a technologist to a leader. Kathy stresses the significance of developing effective communication habits to bridge this gap. She suggests that CTOs create a playbook of best practices to enhance their communication skills and join communities of other CTOs to learn from their experiences. Matching the right CTO to the stage of a company is another crucial aspect discussed in the episode. Kathy explains that different stages of a company require different types of CTOs, and it is essential to find the right fit. To navigate these challenges, Kathy advises CTOs to build a support system of advisors and coaches who can provide guidance and help them overcome obstacles. Additionally, she encourages CTOs to be aware of their own preferences and strengths, as self-awareness can greatly contribute to their success. In conclusion, this podcast episode sheds light on the technical aspects of being a CTO and the challenges they face. Kathy Keating's insights provide valuable guidance for CTOs to build trust, develop effective communication habits, match their skills to the company's stage, and create a support system for their professional growth. By understanding these key technical aspects, CTOs can enhance their leadership skills and contribute to the success of their organizations....