typescript 53 lines · 9 steps

Reactive filtering with Angular signals

An Angular standalone component uses signal inputs, models, and computed values to filter a product list without manual change detection.

Explained by highlit
1import { Component, computed, input, model, signal } from '@angular/core';
2import { FormsModule } from '@angular/forms';
3 
4interface Product {
5 id: number;
6 name: string;
7 category: string;
8 price: number;
9}
10 
11@Component({
12 selector: 'app-product-filter',
13 standalone: true,
14 imports: [FormsModule],
15 template: `
16 <div class="filters">
17 <input
18 type="search"
19 placeholder="Search products…"
20 [(ngModel)]="query" />
21 
22 <select [(ngModel)]="category">
23 <option value="">All categories</option>
24 @for (cat of categories(); track cat) {
25 <option [value]="cat">{{ cat }}</option>
26 }
27 </select>
28 
29 <span class="count">{{ visible().length }} of {{ products().length }}</span>
30 </div>
31 `,
32})
33export class ProductFilterComponent {
34 readonly products = input.required<Product[]>();
35 
36 readonly query = model('');
37 readonly category = model('');
38 
39 protected readonly categories = computed(() =>
40 [...new Set(this.products().map((p) => p.category))].sort(),
41 );
42 
43 readonly visible = computed(() => {
44 const term = this.query().trim().toLowerCase();
45 const cat = this.category();
46 
47 return this.products().filter((p) => {
48 const matchesCategory = !cat || p.category === cat;
49 const matchesTerm = !term || p.name.toLowerCase().includes(term);
50 return matchesCategory && matchesTerm;
51 });
52 });
53}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1computed signals derive state automatically and recompute only when their dependencies change.
  2. 2model signals give two-way [(ngModel)] binding while staying part of the reactive graph.
  3. 3Deriving values from signals removes the need for manual change-detection or subscription cleanup.

Related explainers

typescript
import { registerLocaleData } from '@angular/common';
import localeFr from '@angular/common/locales/fr';
import localeFrExtra from '@angular/common/locales/extra/fr';
import localeDe from '@angular/common/locales/de';

Locale-aware bootstrapping in Angular

i18n localization dependency-injection
Intermediate 8 steps
typescript
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import * as Joi from 'joi';
 

Validating env config at boot in NestJS

configuration schema-validation environment-variables
Intermediate 8 steps
typescript
import { Inject, Injectable, Logger } from '@nestjs/common';
import { CACHE_MANAGER } from '@nestjs/cache-manager';
import { Cache } from 'cache-manager';
import { InjectRepository } from '@nestjs/typeorm';

A cache-aside country lookup in NestJS

cache-aside dependency-injection batch-lookup
Intermediate 8 steps
typescript
import { Injectable, effect, signal, computed } from '@angular/core';
 
interface Preferences {
  theme: 'light' | 'dark';

A signal-based preferences store in Angular

signals state-management persistence
Intermediate 7 steps
typescript
import { useEffect, useState } from "react";
 
interface Section {
  id: string;

Building a scroll-spy hook in React

custom-hooks intersectionobserver dom-observation
Intermediate 8 steps
typescript
import { Injectable, Scope, Inject, NotFoundException } from '@nestjs/common';
import { REQUEST } from '@nestjs/core';
import { Request } from 'express';
import { DataSource } from 'typeorm';

Per-tenant database connections in NestJS

multi-tenancy connection-pooling dependency-injection
Advanced 8 steps

Share this explainer

Here's the card — post it anywhere.

Reactive filtering with Angular signals — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code