# ⚡ Getting started with Angular Signals: a beginner's guide

---

## 🤔 What are Signals — and why should you care?

If you've been writing Angular for a while, you've probably used `ngOnChanges`, `BehaviorSubject`, or just mutated a property and hoped the template would re-render. It mostly worked, but it was never quite clean.

**Signals are Angular's new, built-in way to manage reactive state.** A Signal is simply a value that Angular knows about — and when that value changes, Angular knows to update only the parts of the UI that depend on it.

No subscriptions. No `async` pipe. No manual `detectChanges()`. Just a value, and the guarantee that Angular stays in sync with it. 🎯

Signals became fully stable in **Angular 20** and are now the recommended way to write reactive Angular code.

---

## 🚀 Your first signal: `signal()`

Creating a Signal is one line:

```typescript
import { signal } from '@angular/core';

count = signal(0);
```

You've just created a reactive counter with an initial value of `0`. That's it! 🎉

### 📖 Reading a signal

To read the current value, **call it like a function**:

```typescript
console.log(this.count()); // 0
```

This is the most important thing to remember: signals are functions. `count` is not the value — `count()` is.

### ✏️ Writing to a signal

Two methods let you change a signal's value:

```typescript
this.count.set(5);             // set to a specific value
this.count.update(n => n + 1); // update based on current value
```

- Use `.set()` when you have a new value to replace the old one.
- Use `.update()` when the new value depends on the current one (like incrementing a counter).

### 🖼️ Using signals in templates

Signals work directly in Angular templates:

```html
<p>Count: {{ count() }}</p>
<button (click)="count.update(n => n + 1)">+1</button>
<button (click)="count.set(0)">Reset</button>
```

Angular automatically tracks which signals a template reads, and re-renders only when those signals change. No `ChangeDetectorRef`, no zone triggers needed. ✅

---

## 🧮 Derived state: `computed()`

Often you need a value that is automatically derived from other signals. That's what `computed()` is for.

```typescript
import { signal, computed } from '@angular/core';

price    = signal(100);
quantity = signal(3);

total = computed(() => this.price() * this.quantity());
```

`total` is now a **read-only signal** that automatically stays in sync:

```typescript
console.log(this.total()); // 300

this.quantity.set(5);
console.log(this.total()); // 500 — updated automatically ✨
```

### 📌 Key rules about `computed()`

**🦥 It is lazy.** Angular only recalculates it when something actually reads it, and only if one of its dependencies changed. This makes it very efficient.

**🔒 It is read-only.** You cannot call `.set()` on a computed signal. It always reflects its source signals.

**🔍 It tracks dependencies automatically.** Whatever signals you read inside the computation function become dependencies. No need to declare them.

```typescript
// Angular tracks both firstName and lastName automatically 🤝
fullName = computed(() => `${this.firstName()} ${this.lastName()}`);
```

### 🛒 A real-world computed example

```typescript
@Component({ /* ... */ })
export class CartComponent {
  items    = signal<CartItem[]>([]);
  discount = signal(0);   // percentage, e.g. 10 for 10%

  subtotal = computed(() =>
    this.items().reduce((sum, item) => sum + item.price * item.qty, 0)
  );

  discountAmount = computed(() =>
    this.subtotal() * (this.discount() / 100)
  );

  total = computed(() =>
    this.subtotal() - this.discountAmount()
  );
}
```

The template just reads the signals — it never needs to worry about when things recalculate:

```html
<p>Subtotal: {{ subtotal() | currency }}</p>
<p>Discount: -{{ discountAmount() | currency }}</p>
<p><strong>Total: {{ total() | currency }}</strong></p>
```

Clean, predictable, and zero boilerplate. 💪

---

## ⚙️ Side effects: `effect()`

Sometimes you need to react to a signal change and do something that isn't just returning a new value — like logging, calling a non-Angular API, or syncing to `localStorage`. That's `effect()`. 🌍

```typescript
import { signal, effect } from '@angular/core';

theme = signal<'light' | 'dark'>('light');

constructor() {
  effect(() => {
    // Runs whenever theme() changes 🌗
    document.body.setAttribute('data-theme', this.theme());
  });
}
```

Like `computed()`, `effect()` automatically tracks which signals it reads. It re-runs whenever any of them change.

### ⚠️ Important rules for `effect()`

**🚫 Use it for side effects only.** If you find yourself setting another signal inside an `effect()`, stop — that's almost always a sign you should use `computed()` or `linkedSignal()` instead. Effects are for talking to the outside world (DOM, `localStorage`, analytics, etc.), not for deriving state.

**📍 It must be created in an injection context** — typically the constructor or a class field initializer. Angular manages its cleanup automatically.

```typescript
@Component({ /* ... */ })
export class SearchComponent {
  query = signal('');

  constructor() {
    effect(() => {
      // 📊 Logs to analytics whenever query changes
      analytics.track('search', { query: this.query() });
    });
  }
}
```

---

## 📥 Signals as component inputs: `input()`

Angular 17.1 introduced signal-based `input()`, which is now the modern way to receive data from a parent component. 🎁

```typescript
import { Component, input, computed } from '@angular/core';

@Component({ /* ... */ })
export class UserCardComponent {
  // Required input — TypeScript infers Signal<string>
  name     = input.required<string>();

  // Optional input with a default value
  role     = input('Viewer');

  // You can compute from inputs just like any signal 🧠
  initials = computed(() =>
    this.name().split(' ').map(n => n[0]).join('').toUpperCase()
  );
}
```

In the template of the parent:

```html
<app-user-card [name]="'Alice Johnson'" [role]="'Admin'" />
```

The big benefit: `input()` returns a real `Signal<T>`. You can use it in `computed()`, chain it with other signals, and Angular tracks it like any other reactive value. 🔗

---

## ⚔️ Signals vs the old way: a quick comparison

| Situation | 😓 Old approach | 😍 With Signals |
|-----------|----------------|-----------------|
| Reactive state | `BehaviorSubject` + `async` pipe | `signal()` |
| Derived value | `map()` in RxJS pipeline | `computed()` |
| Side effect on change | `subscribe()` + manual cleanup | `effect()` |
| Component input | `@Input()` decorator | `input()` |
| Change detection | Zone.js magic 🪄 | Signal-aware, explicit ✅ |

> 💡 Signals don't replace RxJS entirely — async streams, event buses, and complex pipelines still benefit from RxJS. But for component state and derived values, Signals are cleaner and more efficient.

---

## 🏗️ A complete example: product search

Let's put it all together in a realistic component:

```typescript
import { Component, signal, computed, effect } from '@angular/core';

interface Product {
  id: number;
  name: string;
  price: number;
  category: string;
}

@Component({
  selector: 'app-product-search',
  template: `
    <input [value]="query()" (input)="query.set($event.target.value)"
           placeholder="🔍 Search products..." />

    <select [value]="category()" (change)="category.set($event.target.value)">
      <option value="">All categories</option>
      <option value="electronics">Electronics</option>
      <option value="clothing">Clothing</option>
    </select>

    <p>{{ filteredProducts().length }} results found</p>

    @for (product of filteredProducts(); track product.id) {
      <div class="product-card">
        <strong>{{ product.name }}</strong>
        <span>{{ product.price | currency }}</span>
      </div>
    }
  `
})
export class ProductSearchComponent {
  // 📦 State signals
  allProducts = signal<Product[]>([...]);
  query       = signal('');
  category    = signal('');

  // 🧮 Derived signal — recomputes only when query or category changes
  filteredProducts = computed(() => {
    const q   = this.query().toLowerCase();
    const cat = this.category();

    return this.allProducts().filter(p =>
      p.name.toLowerCase().includes(q) &&
      (!cat || p.category === cat)
    );
  });

  constructor() {
    // 💾 Side effect: save last search to localStorage
    effect(() => {
      localStorage.setItem('lastQuery', this.query());
    });
  }
}
```

No subscriptions. No `ngOnChanges`. No manual change detection. The template is always in sync. 🙌

---

## 📋 Summary: the three primitives

| Primitive | Import | Writable? | Use for |
|-----------|--------|-----------|---------|
| `signal(value)` 📦 | `@angular/core` | ✅ Yes | Mutable state your component owns |
| `computed(() => ...)` 🧮 | `@angular/core` | ❌ No | Values derived from other signals |
| `effect(() => ...)` ⚙️ | `@angular/core` | n/a | Side effects when signals change |

Start with these three. Once they feel natural, you're ready to explore `linkedSignal()` and `resource()` — Angular's more advanced reactive APIs covered in the companion post to this one. 🚀

---

## 📚 Further reading

- 📖 [Angular docs: Signals overview](https://angular.dev/guide/signals)
- 🧮 [Angular docs: `computed()`](https://angular.dev/guide/signals#computed-signals)
- ⚙️ [Angular docs: `effect()`](https://angular.dev/guide/signals#effects)
- 📥 [Angular docs: Signal inputs](https://angular.dev/guide/components/inputs)
