Angular 16 Is Out Now: Learn How to Replace RxJS with Signals

Signals are meant to replace RxJS to simplify your reactive code.

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

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. The 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 :).

Search & Pagination (RxJS)

Here is the RxJS code of a 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 a code review for these parts. While I have been talking with my friends — each one of them found different things to improve in my initial code and had a different vision of 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!

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)

Exactly 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

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 badly I behaved. The example here is based on the initial developer preview of Signals available in Angular 16 on its release date.

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

Build Angular Apps with reusable components, just like Lego

Bit’s open-source tool help 250,000+ devs to build apps with components.

Turn any UI, feature, or page into a reusable component — and share it across your applications. It’s easier to collaborate and build faster.

Introduction to Angular | Bit

Learn more

Split apps into components to make app development easier, and enjoy the best experience for the workflows you want:

Micro-Frontends

Design System

Code-Sharing and reuse

Monorepo

Learn more:


Angular 16 Is Out Now: Learn How to Replace RxJS with Signals was originally published in Bits and Pieces on Medium, where people are continuing the conversation by highlighting and responding to this story.


This content originally appeared on Bits and Pieces - Medium and was authored by Daniel Glejzner

Signals are meant to replace RxJS to simplify your reactive code.

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

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. The 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 :).

Search & Pagination (RxJS)

Here is the RxJS code of a 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 a code review for these parts. While I have been talking with my friends — each one of them found different things to improve in my initial code and had a different vision of 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!

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)

Exactly 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

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 badly I behaved. The example here is based on the initial developer preview of Signals available in Angular 16 on its release date.

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

Build Angular Apps with reusable components, just like Lego

Bit’s open-source tool help 250,000+ devs to build apps with components.

Turn any UI, feature, or page into a reusable component — and share it across your applications. It’s easier to collaborate and build faster.

Introduction to Angular | Bit

Learn more

Split apps into components to make app development easier, and enjoy the best experience for the workflows you want:

Micro-Frontends

Design System

Code-Sharing and reuse

Monorepo

Learn more:


Angular 16 Is Out Now: Learn How to Replace RxJS with Signals was originally published in Bits and Pieces on Medium, where people are continuing the conversation by highlighting and responding to this story.


This content originally appeared on Bits and Pieces - Medium and was authored by Daniel Glejzner


Print Share Comment Cite Upload Translate Updates
APA

Daniel Glejzner | Sciencx (2023-05-08T10:45:57+00:00) Angular 16 Is Out Now: Learn How to Replace RxJS with Signals. Retrieved from https://www.scien.cx/2023/05/08/angular-16-is-out-now-learn-how-to-replace-rxjs-with-signals-2/

MLA
" » Angular 16 Is Out Now: Learn How to Replace RxJS with Signals." Daniel Glejzner | Sciencx - Monday May 8, 2023, https://www.scien.cx/2023/05/08/angular-16-is-out-now-learn-how-to-replace-rxjs-with-signals-2/
HARVARD
Daniel Glejzner | Sciencx Monday May 8, 2023 » Angular 16 Is Out Now: Learn How to Replace RxJS with Signals., viewed ,<https://www.scien.cx/2023/05/08/angular-16-is-out-now-learn-how-to-replace-rxjs-with-signals-2/>
VANCOUVER
Daniel Glejzner | Sciencx - » Angular 16 Is Out Now: Learn How to Replace RxJS with Signals. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2023/05/08/angular-16-is-out-now-learn-how-to-replace-rxjs-with-signals-2/
CHICAGO
" » Angular 16 Is Out Now: Learn How to Replace RxJS with Signals." Daniel Glejzner | Sciencx - Accessed . https://www.scien.cx/2023/05/08/angular-16-is-out-now-learn-how-to-replace-rxjs-with-signals-2/
IEEE
" » Angular 16 Is Out Now: Learn How to Replace RxJS with Signals." Daniel Glejzner | Sciencx [Online]. Available: https://www.scien.cx/2023/05/08/angular-16-is-out-now-learn-how-to-replace-rxjs-with-signals-2/. [Accessed: ]
rf:citation
» Angular 16 Is Out Now: Learn How to Replace RxJS with Signals | Daniel Glejzner | Sciencx | https://www.scien.cx/2023/05/08/angular-16-is-out-now-learn-how-to-replace-rxjs-with-signals-2/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.