In this article, I will walk you through creating a simple reactive application using Angular signals to display the prices of various cryptocurrencies like Bitcoin, Ethereum, and Dogecoin.

This will help you understand how to leverage Angular signals for building reactive applications and how they can benefit you.

What are Angular Signals?

Angular signals are reactive values that can be observed, updated, and notify dependents when a value changes. They provide a simple way to create reactive applications without the need for complex libraries like RxJS. Signals can automatically track their dependencies, allowing different parts of the application to automatically update in response to changes in data.

Creating the Angular Signals Crypto Application

To build a reactive crypto application, we will follow these steps:

  • Set up the Angular project
  • Create signals for cryptocurrencies and their prices
  • Create computed signals to display the total value of each cryptocurrency
  • Update the template to display the data
  • Style the application to look modern and cool

Set up the Angular project

First, create a new Angular project using the Angular CLI:

npx @angular/cli@next new reactive-crypto-app

Navigate to the newly created project directory and open it in your favorite code editor.

Create signals for cryptocurrencies and their prices

In the app.component.ts file, import the necessary components:

import { Component, signal, computed } from '@angular/core';

Define the component decorator:

@Component({
  selector: 'app-root',
  template: `
    <div class="container">
      <!-- content here -->
    </div>
  `,
})
export class AppComponent {
  // component code here
}

This is an Angular component that we define using the @Component decorator. It has a selector that we can use to reference it in our HTML code, and a template that defines the HTML markup for the component.

We define the signals for the selected cryptocurrency and the quantity:

public selectedCrypto = signal({ name: 'Bitcoin', price: 30000 });
public quantity = signal(2);

Here, we use the signal function to define two signals: selectedCrypto and quantity. selectedCrypto is initialized with an object that has a name of ‘Bitcoin’ and a price of 30000, while quantity is initialized with a value of 2.

We define a computed signal for the total price:

public totalPrice = computed(() => this.selectedCrypto().price * this.quantity());

This uses the computed function to define a signal called totalPrice. It calculates the total price of the selected cryptocurrency based on its price and the quantity using a function that is executed whenever either the selectedCrypto or quantity signals change.

We define the changeCrypto function to update the selected cryptocurrency:

changeCrypto(name: string, price: number) {
  this.selectedCrypto.set({ name, price });
}

This function takes in a name and price for a cryptocurrency and updates the selectedCrypto signal with a new object containing the name and price.

We define the updateQuantity function to update the quantity:

updateQuantity(delta: number) {
  this.quantity.update((q) => Math.max(0, q + delta));
}

This function takes in a delta value that can be either 1 or -1 to increase or decrease the quantity. It uses the update function on the quantity signal to update the value based on the current value of the signal and the delta value. It ensures that the quantity cannot go below 0 by using the Math.max function.

Finally, we define the HTML markup for the component using the template property of the @Component decorator. This markup includes the selected cryptocurrency, quantity, total price, and buttons to change the selected cryptocurrency and the quantity.

The selected cryptocurrency section:

<h5 class="card-title">Selected crypto: {{ selectedCrypto().name }}</h5>

This displays the currently selected cryptocurrency’s name using Angular’s interpolation syntax ({{ }}) to access the name property of the selectedCrypto signal.

The quantity section:

<p class="card-text">Quantity: {{ quantity() }}</p>

This displays the current quantity using interpolation syntax to access the quantity signal.

The total price section:

<p class="card-text">Total Price: {{ totalPrice() }}</p>

This displays the total price of the selected cryptocurrency based on the selectedCrypto and quantity signals using the totalPrice computed signal.

The cryptocurrency selection buttons:

<div class="btn-group" role="group">
  <button (click)="changeCrypto('Bitcoin', 30000)" class="btn btn-primary">Bitcoin</button>
  <button (click)="changeCrypto('Ethereum', 2000)" class="btn btn-primary">Ethereum</button>
  <button (click)="changeCrypto('Dogecoin', 0.1)" class="btn btn-primary">Dogecoin</button>
</div>

This displays three buttons that allow the user to select between Bitcoin, Ethereum, and Dogecoin.

Each button has a (click) event listener that calls the changeCrypto function with the appropriate name and price values for the selected cryptocurrency.

The quantity adjustment buttons:

<div class="btn-group mt-3" role="group">
  <button (click)="updateQuantity(-1)" class="btn btn-secondary">-</button>
  <button (click)="updateQuantity(1)" class="btn btn-secondary">+</button>
</div>

This displays two buttons that allow the user to increase or decrease the quantity. Each button has a (click) event listener that calls the updateQuantity function with the appropriate value of -1 or 1.

Here’s the full code of app.component.ts

import { Component, signal, computed } from '@angular/core';

@Component({
  selector: 'app-root',
  template: `
    <div class="container">
      <h1 class="my-4">Angular Signals Crypto Store</h1>
      <div class="card my-4">
        <div class="card-body">
          <h5 class="card-title">Selected crypto: {{ selectedCrypto().name }}</h5>
          <p class="card-text">Quantity: {{ quantity() }}</p>
          <p class="card-text">Total Price: {{ totalPrice() }}</p>
          <div class="btn-group" role="group">
          <button (click)="changeCrypto('Bitcoin', 30000)" class="btn btn-primary mx-2">Bitcoin</button>
          <button (click)="changeCrypto('Ethereum', 2000)" class="btn btn-primary mx-2">Ethereum</button>
          <button (click)="changeCrypto('Dogecoin', 0.1)" class="btn btn-primary mx-2">Dogecoin</button>
        </div>
        
        <div class="btn-group mt-3" role="group">
          <button (click)="updateQuantity(-1)" class="btn btn-secondary mx-2">-</button>
          <button (click)="updateQuantity(1)" class="btn btn-secondary mx-2">+</button>
        </div>
        
        </div>
      </div>
    </div>
  `,
})
export class AppComponent {
  public selectedCrypto = signal({ name: 'Bitcoin', price: 30000 });
  public quantity = signal(2);
  public totalPrice = computed(() => this.selectedCrypto().price * this.quantity());

  changeCrypto(name: string, price: number) {
    this.selectedCrypto.set({ name, price });
  }

  updateQuantity(delta: number) {
    this.quantity.update((q) => Math.max(0, q + delta));
  }
}

Wrapping up

In the example above, we’re using Angular Signals to manage the state of the cryptocurrency prices, amounts, and total values.

Using Signals has several benefits over using plain RxJS, and I’ll explain them step by step.

Simplified state management: With Angular Signals, the state of your component is managed using simple, reactive variables.

Each signal represents a piece of state and is a getter function.

This makes it easier to understand the structure and flow of data in your application, as opposed to using RxJS, where you might have to work with Observables, Subjects, and BehaviorSubjects to achieve a similar outcome.

Clear dependencies and reactivity: In the example, we use computed signals to represent the total value of each cryptocurrency.

Computed signals are reactive and automatically update when any of their dependencies change.

This makes it easy to see how different parts of the state are related to each other and how they update in response to changes.

In contrast, with RxJS, you would need to use operators like combineLatest, map, or withLatestFrom to achieve the same behavior, which can be more complex and harder to follow.

Easier updates: Signals provide methods like set, update, and mutate to modify their values.

In the example, we use the update method to change the amount of each cryptocurrency when the user clicks the Increase or Decrease buttons.

With RxJS, you would need to work with Subjects and manage subscriptions to handle updates, which can be more verbose and require more boilerplate code.

Fewer side effects: Angular Signals promote a more functional and declarative programming style. Since signals are pure getter functions, you don’t need to worry about side effects when working with them.

With RxJS, you might need to handle side effects by using tap, shareReplay, or other operators, which can make your code harder to reason about and maintain.

Reduced complexity: Angular Signals are designed to be simple and easy to understand. They abstract away some of the complexity that comes with reactive programming in RxJS.

For example, you don’t need to worry about managing subscriptions, unsubscribing from observables, or handling memory leaks, as Angular Signals handle these concerns for you.

In summary, using Angular Signals instead of RxJS in this example results in simpler state management, clearer dependency tracking, easier updates, fewer side effects, and reduced complexity.

While RxJS is a powerful library for reactive programming, Angular Signals provide a more straightforward and intuitive way to handle reactive state in your application.