Angular 16 is out now: Learn how to Replace RxJS with Signals
Tags: angular, signals, rxjs, typescript
Published: 2023-05-06 06:41:50


## Angular 16 is out now: Learn how to Replace RxJS with Signals

Here I have one specific real world example for you, raw code comparison. Nothing more, nothing less!

![](https://cdn-images-1.medium.com/max/3614/1*QFXlITTEEpJtuopRmXPguw.jpeg)

## But but but… Signals & RxJS right — it’s not supposed to replace it?

Couldn’t help myself and spark a little controversy in the title. Example I have is completely replacing RxJS implementation with Signals.

**Signals are meant to replace RxJS to simplify your reactive code. However, only synchronous RxJS code** — I wouldn’t replace asynchronous parts :).

![](https://cdn-images-1.medium.com/max/3072/1*AoVcZaCh5W6abfAbbEC0YQ.png)

## Search & Pagination (RxJS)

Here is RxJS code of small feature that allows you to search for a user and use pagination. It’s meant to showcase how synchronous **RxJS code can be simplified with Signals.**

I asked for code review for this parts. While I have been talking with my friends — **each one of them found different things to improve in my initial code and had different vision on how this could look like.**

Additionally there have been occasional bugs and potential memory leaks. **This is exactly why working with synchronous RxJS is not ideal.** I also bet that looking at this example you also have a different kind of implementation in mind!
```typescript
    const users = [
      { id: 1, name: 'Spiderman' },
      { id: 2, name: 'Hulk' },
      { id: 3, name: 'Wolverine' },
      { id: 4, name: 'Cyclops' },
      { id: 5, name: 'Venom' },
    ];
    
    @Component({
      selector: 'my-app',
      standalone: true,
      imports: [CommonModule, FormsModule],
      template: `
      <input [ngModel]="searchInput$ | async" (ngModelChange)="searchUser($event)" placeholder="Search">
    
      <ul>
        <li *ngFor="let user of paginatedAndFilteredUsers$ | async">{{ user.name }}</li>
      </ul>
    
      <button (click)="goToPrevPage()">Previous</button>
      pag. {{ currentPage$ | async }}
      <button (click)="goToNextPage()">Next</button>
    `,
    })
    export class App {
      readonly firstPage = 1;
    
      itemsPerPage = 2;
    
      searchInput$ = new BehaviorSubject('');
      currentPage$ = new BehaviorSubject(this.firstPage);
    
      paginatedAndFilteredUsers$ = combineLatest([
        this.currentPage$.pipe(distinctUntilChanged()), // trigger only when it actually changes
        this.searchInput$.pipe(
          distinctUntilChanged(),
          map((searchText) =>
            users.filter((user) =>
              user.name.toLowerCase().includes(searchText.toLowerCase())
            )
          )
        ),
      ]).pipe(
        map(([currentPage, filteredUsers]) => {
          const startIndex = (currentPage - 1) * this.itemsPerPage;
          const endIndex = startIndex + this.itemsPerPage;
          return filteredUsers.slice(startIndex, endIndex);
        })
      );
    
      searchUser(searchText: string): void {
        this.searchInput$.next(searchText);
        if (this.currentPage$.value > this.firstPage) {
          this.currentPage$.next(this.firstPage);
        }
      }
    
      goToPrevPage(): void {
        this.currentPage$.next(Math.max(this.currentPage$.value - 1, 1));
      }
    
      goToNextPage(): void {
        this.currentPage$.next(
          Math.min(this.currentPage$.value + 1, this.itemsPerPage + 1)
        );
      }
    }
```
![](https://cdn-images-1.medium.com/max/3072/1*sGw-QOhBGF11c8izd_zDbw.png)

## Search & Pagination (Signals)

Exactly the same implementation but done with Signals

```typescript
    const users = [
      { id: 1, name: 'Spiderman' },
      { id: 2, name: 'Hulk' },
      { id: 3, name: 'Wolverine' },
      { id: 4, name: 'Cyclops' },
      { id: 5, name: 'Venom' },
    ];
    
    @Component({
      selector: 'my-app',
      standalone: true,
      imports: [CommonModule, FormsModule],
      template: `
      <input [ngModel]="searchInput()" (ngModelChange)="searchUser($event)" placeholder="Search">
      
      <ul>
        <li *ngFor="let user of paginatedAndFilteredUsers()">{{ user.name }}</li>
      </ul>
    
      <button (click)="goToPrevPage()">Previous</button>
      pag. {{ currentPage() }}
      <button (click)="goToNextPage()">Next</button>
    `,
    })
    export class App {
      readonly firstPage = 1;
    
      itemsPerPage = 2;
    
      searchInput = signal('');
      currentPage = signal(this.firstPage);
    
      paginatedAndFilteredUsers = computed(() => {
        const startIndex = (this.currentPage() - 1) * this.itemsPerPage;
        const endIndex = startIndex + this.itemsPerPage;
        return users
          .filter((user) =>
            user.name.toLowerCase().includes(this.searchInput().toLowerCase())
          )
          .slice(startIndex, endIndex);
      });
    
      searchUser(searchText: string): void {
        this.searchInput.set(searchText);
        if (this.currentPage() > this.firstPage) {
          this.currentPage.set(this.firstPage);
        }
      }
    
      goToPrevPage(): void {
        this.currentPage.update((currentPage) => Math.max(currentPage - 1, 1));
      }
    
      goToNextPage(): void {
        this.currentPage.update((currentPage) =>
          Math.min(currentPage + 1, this.itemsPerPage + 1)
        );
      }
    }
```
![](https://cdn-images-1.medium.com/max/3072/1*AfQHXYUEMl486HMntWK5zA.png)

## Now comparison one by one

```typescript
    //RxJs
    @Component({
      selector: 'my-app',
      standalone: true,
      imports: [CommonModule, FormsModule],
      template: `
      <input [ngModel]="searchInput$ | async" (ngModelChange)="searchUser($event)" placeholder="Search">
    
      <ul>
        <li *ngFor="let user of paginatedAndFilteredUsers$ | async">{{ user.name }}</li>
      </ul>
    
      <button (click)="goToPrevPage()">Previous</button>
      pag. {{ currentPage$ | async }}
      <button (click)="goToNextPage()">Next</button>
    `,
    })
    
    // Signals
    @Component({
      selector: 'my-app',
      standalone: true,
      imports: [CommonModule, FormsModule],
    template: `
      <input [ngModel]="searchInput()" (ngModelChange)="searchUser($event)" placeholder="Search">
      
      <ul>
        <li *ngFor="let user of paginatedAndFilteredUsers()">{{ user.name }}</li>
      </ul>
    
      <button (click)="goToPrevPage()">Previous</button>
      pag. {{ currentPage() }}
      <button (click)="goToNextPage()">Next</button>
    `,
    })
```
```typescript
    //RxJS
    readonly firstPage = 1;
    
      itemsPerPage = 2;
    
      searchInput$ = new BehaviorSubject('');
      currentPage$ = new BehaviorSubject(this.firstPage);
    
      paginatedAndFilteredUsers$ = combineLatest([
        this.currentPage$.pipe(distinctUntilChanged()),
        this.searchInput$.pipe(
          distinctUntilChanged(),
          map((searchText) =>
            users.filter((user) =>
              user.name.toLowerCase().includes(searchText.toLowerCase())
            )
          )
        ),
      ]).pipe(
        map(([currentPage, filteredUsers]) => {
          const startIndex = (currentPage - 1) * this.itemsPerPage;
          const endIndex = startIndex + this.itemsPerPage;
          return filteredUsers.slice(startIndex, endIndex);
        })
      );
    
    //Signals
    readonly firstPage = 1;
    
      itemsPerPage = 2;
    
      searchInput = signal('');
      currentPage = signal(this.firstPage);
    
      paginatedAndFilteredUsers = computed(() => {
        const startIndex = (this.currentPage() - 1) * this.itemsPerPage;
        const endIndex = startIndex + this.itemsPerPage;
        return users
          .filter((user) =>
            user.name.toLowerCase().includes(this.searchInput().toLowerCase())
          )
          .slice(startIndex, endIndex);
      });
```
```typescript
    //RxJS
    searchUser(searchText: string): void {
        this.searchInput$.next(searchText);
        if (this.currentPage$.value > this.firstPage) {
          this.currentPage$.next(this.firstPage);
        }
      }
    
    //Signals
    searchUser(searchText: string): void {
        this.searchInput.set(searchText);
        if (this.currentPage() > this.firstPage) {
          this.currentPage.set(this.firstPage);
        }
      }
```
```typescript
    //RxJS
    goToPrevPage(): void {
        this.currentPage$.next(Math.max(this.currentPage$.value - 1, 1));
      }
    
    goToNextPage(): void {
        this.currentPage$.next(
          Math.min(this.currentPage$.value + 1, this.itemsPerPage + 1)
        );
      }
    
    //Signals
    goToPrevPage(): void {
        this.currentPage.update((currentPage) => Math.max(currentPage - 1, 1));
      }
    
    goToNextPage(): void {
        this.currentPage.update((currentPage) =>
          Math.min(currentPage + 1, this.itemsPerPage + 1)
        );
      }
```
## Conclusion

Please remember that currently Signals are in developer preview only! However they are here to stay — and I have to say I love how they replace and simplify code initially written with synchronous RxJS in mind.

### Disclaimer

_If you are reading this in 2024 it means — I haven’t updated this content. You should find me and tell me how bad I behaved. Example here is based on initial developer preview of Signals available in Angular 16 on it’s release date._

_This is going to change once full vision of Signals is available. If you tracked RFCs and proposals — you might immediately think that example presented would work differently with input based signals that are still not available to use._

---

### I hope you liked my article!

If you did you might also like what I am doing on **Twitter**. I am hosting live Twitter Spaces about Angular with GDEs & industry experts! You can participate live, ask your questions or watch replays in a form of short clips :)

If you are interested drop me a follow on **Twitter [@DanielGlejzner](https://twitter.com/DanielGlejzner) — would mean a lot :). Thank You!**