NashTech Insights

Let’s learn about Signals In Angular

Alka Vats
Alka Vats
Table of Contents
signals

Signals are intended to simplify your reactive code by replacing RxJS.

What are the Signals?

In Angular, a signal is not a new concept. SolidJS, React, and Vue.js already support the idea. Ryan Carniato, the creator of SolidJS, served as the source of inspiration.

The value of the signal can be tracked, and if it changes, all of the dependencies that are related are automatically updated.

const counter = signal(0);

It is an object with a value that changes over time, a getter, and setter. All interested customers can be notified by this “reactive” value.

As a result, I have one specific real-world example for you: comparing signals and RxJs’ raw code. That’s it, nothing less!

signals desc

Signals are intended to simplify your reactive code by replacing RxJS. However, only synchronous RxJS code; asynchronous components would not be substituted:).

It is one of the new features of Angular 16, and if you want to learn more about it or about GraphQL, you can refer to this page.

Search & Pagination (RxJS)

This is the RxJS code for a small feature that lets you use pagination and search for a user. It aims to demonstrate how Signals can simplify synchronous RxJS code.

Additionally, there have been some bugs and possible memory leaks on occasion. Working with synchronous RxJS is not ideal for this very reason. You probably have a different idea of how this example should be implemented, too!

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)
    );
  }
}

Search & Pagination (Signals)

Precisely the same implementation but done with Signals.

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)
    );
  }
}

Now comparison one by one

//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>
`,
})
//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);
  });
//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);
    }
  }
//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

So, let’s wrap up everything about signals now. Please keep in mind that Signals are currently only available for developer preview! Notwithstanding, they are staying put — and I need to say I love the delightful way they supplant and work on code at first composed in light of simultaneous RxJS.

Finally, for more such posts, please follow our LinkedIn page- FrontEnd Competency.

Alka Vats

Alka Vats

Alka Vats is a Software Consultant at Nashtech. She is passionate about web development. She is recognized as a good team player, a dedicated and responsible professional, and a technology enthusiast. She is a quick learner & curious to learn new technologies. Her hobbies include reading books, watching movies, and traveling.

Leave a Comment

Your email address will not be published. Required fields are marked *

Suggested Article

%d bloggers like this: