Angular Signals vs RxJS - Reuse It Effectively
Angular Signals vs RxJS - Reuse It Effectively

In this video I want to write a service by using RxJS and then convert it to the signals so you can see the difference.

Signals is a new approach inside Angular and a lot of people consider that this is a full replacement of RxJS. Essentially it is not true but for some cases it is extremely easy to convert RxJS to signals and the code is much more readable and easier to support.

RxJS service

So we want to implement an input where you can type new users and they must be rendered on the screen when we submit the form.

Here we want to start with our RxJS service.

// src/app/users.service.ts
export class UsersService {
  private users$ = new BehaviorSubject<UserInterface>([])

  getUsers(): Observable<UserInterface[]> {
    return this.users$.asObservable()
  }

  addUser(user: UserInterface): void {
    const updatedUsers = [...this.users$.getValue(), user]
    this.users$.next(updatedUsers)
  }

  removeUser(userId: string): void {
    const updatedUsers = this.users$.getValue().filter(user => user.id !== userId)
    this.users$.next(updatedUsers)
  }
}

Here we created our UsersService. To store data inside we create a BehaviorSubject. But we made it private and exposed it only as an observable inside getUsers method.

Additionally we created addUser and removeUser which update our BehaviorSubject by using .next().

Now we must update our component to use this service for the input.

export class AppComponent {
  usersService = inject(UsersService);
  fb = inject(FormBuilder);
  addForm = this.fb.nonNullable.group({
    name: '',
  });
  users$ = this.usersService.getUsers();
}

Here we injected UsersService and saved a users stream in a property. Now we can render our users in the template.

<form [formGroup]="addForm" (ngSubmit)="onUserAdd()">
  <input type="text" formControlName="name" />
</form>

<div *ngFor="let user of users$ | async">
  {{ user.name }}
  <span (click)="removeUser(user.id)">X</span>
</div>

We rendered our list of users with ngFor and added removeUser click event which must remove a user.

Now let's add this function and update our onUserAdd function.

onUserAdd(): void {
  const user: UserInterface = {
    id: Math.random().toString(),
    name: this.addForm.getRawValue().name,
  };
  this.usersService.addUser(user);
  this.addForm.reset();
}

removeUser(userId: string): void {
  this.usersService.removeUser(userId);
}

In both functions we just call our service and provide some data inside.

Working project

As you can see we can add and remove users to our list.

Using Angular signals

Now let's refactor this service to Angular signals.

export class UsersService {
  private usersSig = signal<UserInterface[]>([]);

  getUsers(): Signal<UserInterface[]> {
    return computed(this.usersSig);
  }

  addUser(user: UserInterface): void {
    this.usersSig.update((users) => [...users, user]);
  }

  removeUser(userId: string): void {
    const updatedUsers = this.usersSig().filter((user) => user.id !== userId);
    this.usersSig.set(updatedUsers);
  }
}

It looks really similar but now we store our list in a signal. We also return a computed signal to make it readonly in getUsers. Our addUser and removeUser functions use .update to change the list of users.

Now let's make changes to our component.

usersSig = this.usersService.getUsers();

We just need to update a single line which returns a signal now.

<div *ngFor="let user of usersSig()">
  {{ user.name }}
  <span (click)="removeUser(user.id)">X</span>
</div>

And to render a list of users to don't use an async pipe anymore but we read a signal value.

Working project

As you can see our project work just like before but now we built everything with signals.

And actually if you want to learn Angular with NgRx from empty folder to a fully functional production application make sure to check my Angular and NgRx - Building Real Project From Scratch course.

📚 Source code of what we've done