Telerik blogs

With Signals stable in Angular 17, we can build reactive applications without RxJS, or combine RxJS with Signals. Let’s take a look!

If you’re planning to build a new app with Angular 17, with or without module-less architecture, it’s crucial to pay attention to a powerful feature in Angular for building reactive apps: Angular Signals. They help us create more dynamic and reactive user interfaces without complex code, making it easier to handle and manage application state and behaviors.

Read more about Angular 17’s new features.

Nowadays, we often use RxJS to create reactive applications. RxJS is great, but sometimes it can be overkill, like using a truck to travel instead of a car.

The Angular team knows this situation well. When a new developer starts with Angular, RxJS can be powerful but complex, with a large API including many operators, pipes and observables, which can take time to feel “confident” with.

Now, in Version 17, we have Signals in stable! So, we can build reactive applications without RxJS, or combine RxJS with Signals. 😏

Will RxJS Disappear or Become Legacy?

No, RxJS is a part of Angular. We have reactive forms, Signals in HTTP, and RxJS helps us a lot when we need to work with async data and events. Signals are highly beneficial for RxJS, as you will use RxJS when really needed, or combine both to take the best of both worlds.

What Are Angular Signals?

Angular Signals represent a new way to build reactive apps, built on top of reactive primitives that emit updates when their underlying values change.

A signal is like a container that holds a value (like a number or a string) and tells other parts of your app when this value changes. When you use a signal, Angular keeps an eye on it, so it knows exactly when and how it’s used.

The Signals API is a small and easy-to-use API, with three main reactive primitives, to tell Angular when and where the data changes.

The three are:

  • Writable signals: These are signals that you can change. For example, if you have a signal for a number, you can increase or decrease this number.
  • Computed signals: Their value depends on other signals. If the signal they depend on changes, they change too.
  • Effects: These are special functions that respond when signal values change.

I think the better way to get on track is with a bit of code, so let’s do it.

Creating Signals

Before we start, a signal is a variable that holds a value, created by the signal() function and provides methods to interact with it.

Let’s create our first signal:

$message = signal<string>('Hello from Signal');

To read the value of the message signal, we use (). For example, to read the message in the template:

 <h1>
    {{message()}}
</h1>

Or in a TypeScript file:

 if (message().length > 0) {
    console.log("I have a message");
}

We can also set a value or update it, by using the methods set and update.

  • set(): Assigns a new value to the signal. Example:
   message.set('You got a new message');
  message(); // Output: "You got a new message";
  • update(): Modifies the value based on the previous state.
   message.update((p) => `${p} !!😁`);
  message(); // Output: "Hello from Signal !!😁"

Computed Signals

  • Computed: A signal that calculates its value or state from other signals, updating reactively.

Example:

 userName = signal('dany');
congratsUser = computed(() => 'Thanks ' + userName());

// Output: Thanks dany

Learn more about Signals.

Effect()

The effect()function allows for executing side effects in response to state changes. For instance, it can be used to reactively update the UI based on user interactions that are not directly linked to a component’s main functionality.

Example:

 effect(() => {    
    if (userName().length === 3) {
        console.log('You have a userName');
    }
});

Learn more about effect.

We’ve learned some basics about signals. Instead of just looking at small examples, it’s better to learn by doing real things. Let’s use what we know about signals in a real-life situation. For example, we can make a feature to save our favorite products or do a search.

Sound difficult? Not really! With our basic understanding of signals, we can do this easily.

Build Favorite and Search in an Existing App

We have been hired to continue building the kendostore we started last time using Progress Kendo UI for Angular. This time, we need to add new features to the current clothes store: a favorite selector and search filter. You must do the following tasks:

  • Allow the user to click on their favorite clothes and show them in a section.
  • Enable clearing of the selected favorites.
  • Filter the current list of products based on the input search.

Clone the project start point.

The Favorites Feature

First, open products.component.ts and declare the variable $favorites as an array of products using signals.

Remember to import the signal function from import { signal } from '@angular/core';

$favorites = signal<Product[]>([]);

Next, declare two methods, addFavorite and clearFavorites. In addFavorites(product: Product), we pass the product into the method and use the signal’s update method to bind the favorites.

 addFavorite(product: Product) {
  this.$favorites.update((p) => [...p, product]);
}

For clearFavorites, use set to assign an empty array to clear the favorites.

 clearFavorites(): void {
  this.$favorites.set([]);
}

In the products.component.html template, call addFavorite with the current product to update the products signals array.

 <kendo-card-footer class="k-hstack">
    <span>Price {{ product.price | currency }}</span>
    <span (click)="addFavorite(product)"></span>
</kendo-card-footer>

Add the following markup to show the list of favorite products. We iterate over the favorite array using the new Angular syntax, tracking by product.id, and call clearFavorites to clear the list.

 <div class="favorites">
  @for (product of $favorites(); track product.id) {
    <img [src]="product.image" kendoCardMedia alt="cover_img"/>
  }
  <span (click)="clearFavorites()">🗑</span>
</div>

Save changes and click on the ❤ icon in the product. This will add it to the product list! Yeah!!

If you click on the recycle-bin icon, it deletes all favorites! We are creating a nice feature so easily!

Clicking the heart favorite icon adds items to a little collection at the top

Let’s continue with the “add some” effect to the app! 😎

Making Effects with effect()

We learned about effect() which helps us trigger actions when signal variables change—for example, updating the UI to change the background color of our pages when the number of favorites reaches three.

First, open styles.scss and add a red class.

.red {
  background-color: red;
}

Back in products.component.ts, add effect in the constructor. When the number of favorite products reaches three, add the class to the document body.

Remember to import the effectfunction from import { effect } from '@angular/core';

 export class ProductsComponent {
  $favorites = signal<Product[]>([]);

  constructor() {
    effect(() => {
      if (this.$favorites().length >= 3) {
        document.body.classList.add('red');
      }
    });
  }
}

Save change and add three products, and the background changes! Yeah! We have a fun app, reacting to changes in the signals!

When the user clicks the third item's favorite icon, the background turns red

We have a reactive application triggering an effect with just a few lines of code. Let’s take another step forward build a search function. 🔍

Building a Product Search with RxJS and Signals

For product search functionality, we will combine RxJS and signals. But first, let’s understand the toSignal function (part of RxJS Interop) which makes it easy to transform our observables to signals and vice versa.

Declare the $searchFilter signal, rename $products to productsAPI, and wrap the http request with toSignal function.

 #http = inject(HttpClient);
private $searchFilter = signal<string>('');
private $productsAPI = toSignal(
  this.#http.get<Product[]>('https://fakestoreapi.com/products')
);

Learn more about RxJS Interop.

Next, using the compute function to declare the variable $products, the computed reacts to changes in the $productsAPI array and the searchFilter signal.

 $products = computed(() => {
  return this.$productsAPI()?.filter((p) =>
    p.title.toLowerCase().includes(this.$searchFilter()),
  );
});

Create the updateFilter method to update the searchFilter using the set method, based on user input.

 updateFilter(filter: string) {
  const filterValue = filter.length > 3 ? filter : '';
  this.$searchFilter.set(filterValue);
}

In the products.component.html template, add an input for search, updating the searchFilter signal every time the user types.

<div class="search">
<h3>Search by Product Name </h3>
<input #filter (input)="updateFilter(filter.value)">
</div>

User types mens, and the products filter

Yeah, our products react to the changes! This is the power of signals, computes, and RxJS!

Conclusion

We built easy and nice features using Angular, with the power of Signals and RxJS Interop and of course using in combination with a current application built with Kendo UI.

Combining Signals and RxJS allows you to build nice and reactive apps. Feel free to play with it more and combine it with any of Kendo UI for Angular components—you can try this Angular library for free!

Happy coding!


About the Author

Dany Paredes

Dany Paredes is a Google Developer Expert on Angular and Progress Champion. He loves sharing content and writing articles about Angular, TypeScript and testing on his blog and on Twitter (@danywalls).

Related Posts

Comments

Comments are disabled in preview mode.