TL;DR: Angular Signals sind die Antwort auf die Frage "Muss ich wirklich RxJS lernen, nur um einen Counter zu bauen?" - Mit signal(), computed() und effect() bekommst du reaktives State-Management, das sich anfĂźhlt wie normales TypeScript. Einfach, typsicher und performant.

🤔 Warum brauchen wir Signals?

Hand aufs Herz: Wer hat nicht schon mal eine halbe Stunde damit verbracht, einen BehaviorSubject zu debuggen, nur um festzustellen, dass man unsubscribe() vergessen hat? RxJS ist mächtig - keine Frage. Aber für viele alltägliche Aufgaben in Angular-Komponenten ist es wie mit Kanonen auf Spatzen zu schießen.

Angular hat das erkannt und mit Version 16 Signals eingefĂźhrt. Die Idee dahinter ist simpel: Reaktives State-Management, das du verstehst, ohne vorher einen Doktortitel in Marble-Diagrams zu brauchen.

Jetzt wird's spannend: Signals sind keine Konkurrenz zu RxJS. Sie ergänzen es. Aber fßr komponentenlokalen State sind sie ein absoluter Gamechanger.

Angular – Das moderne Webentwicklungs-Framework
Angular ist eine Plattform fĂźr mobile und Desktop-Webanwendungen. Von Google entwickelt, bietet es eine umfassende LĂśsung fĂźr Frontend-Entwicklung.
Angular: Framework fĂźr dynamische Single Page Applications
Angular ist eines der beliebtesten Frameworks fĂźr moderne Webanwendungen. Erfahre, was Angular ausmacht und wie du damit durchstartest.

⚙️ Die Grundlagen: signal(), computed() und effect()

signal() - Dein reaktiver Container

Ein Signal ist im Grunde ein Wrapper um einen Wert, der Angular automatisch mitteilt, wenn sich etwas ändert. Klingt erstmal simpel - ist es auch.

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

// Signal erstellen
const count = signal(0);

// Wert lesen
console.log(count()); // 0

// Wert setzen
count.set(42);
console.log(count()); // 42

// Wert basierend auf aktuellem Wert updaten
count.update(current => current + 1);
console.log(count()); // 43

Dir fällt wahrscheinlich auf: Kein subscribe(), kein pipe(), kein Observable. Du rufst einfach count() auf und bekommst den aktuellen Wert. Fertig. Angular trackt automatisch, wo du das Signal liest, und aktualisiert nur die betroffenen Stellen im Template.

computed() - Abgeleitete Werte

Wenn du einen Wert hast, der von anderen Signals abhängt, kommt computed() ins Spiel. Stell dir das wie eine Excel-Formel vor: Ändert sich ein Eingabewert, wird das Ergebnis automatisch neu berechnet.

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

const firstName = signal('Max');
const lastName = signal('Mustermann');

// Automatisch aktualisiert, wenn sich firstName oder lastName ändern
const fullName = computed(() => `${firstName()} ${lastName()}`);

console.log(fullName()); // "Max Mustermann"

firstName.set('Erika');
console.log(fullName()); // "Erika Mustermann"

Das SchÜne daran: computed() ist lazy. Der Wert wird erst berechnet, wenn er tatsächlich gelesen wird. Und er wird gecacht - wenn sich die Abhängigkeiten nicht geändert haben, wird nicht neu berechnet. Performance geschenkt.

effect() - Seiteneffekte reagieren auf Änderungen

effect() ist fßr alles, was passieren soll, wenn sich ein Signal ändert - Logging, API-Calls, LocalStorage-Updates, was auch immer.

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

const theme = signal('light');

// Wird automatisch ausgefßhrt, wenn sich theme ändert
effect(() => {
  document.body.classList.toggle('dark-mode', theme() === 'dark');
  console.log(`Theme gewechselt zu: ${theme()}`);
});

// LĂśst den Effect aus
theme.set('dark');
// Console: "Theme gewechselt zu: dark"

Wichtig: Effects laufen im Injection-Context. Das heißt, du erstellst sie typischerweise im Constructor oder in Feldern deiner Komponente. Angular kümmert sich automatisch um das Cleanup, wenn die Komponente zerstört wird. Kein ngOnDestroy nötig.

RxJS – Reactive Extensions Library für JavaScript
RxJS ist eine Bibliothek für reaktive Programmierung mit Observables – ideal für asynchronen und callback-basierten Code.
RxJS: Die Reaktive Revolution in JavaScript
RxJS bringt reaktive Programmierung nach JavaScript. Lerne, wie Observables und Operatoren funktionieren.

🔄 RxJS vs. Signals - Wann nutze ich was?

Hier kommen wir zum Kern der Sache. Signals und RxJS sind keine Feinde - sie haben unterschiedliche Stärken.

Anwendungsfall Signals ✅ RxJS ✅
Komponentenlokaler State ⭐ Perfekt Overkill
Formular-Validierung ⭐ Einfach & direkt Funktioniert, aber verbose
HTTP-Requests Mit resource() ⭐ HttpClient ist RxJS-basiert
WebSocket-Streams Nicht geeignet ⭐ Genau dafßr gemacht
Komplexe Event-Ketten Nicht geeignet ⭐ switchMap, debounceTime & Co
Globaler App-State ⭐ Mit Services Funktioniert auch
UI-Toggle/Counter ⭐ Trivial UnnÜtig komplex

Faustregel: Wenn du einen Wert hast, der sich ändert und du willst, dass die UI reagiert → Signal. Wenn du mit Streams, Timing oder komplexen Event-Ketten arbeitest → RxJS.

💻 Praxisbeispiel: Todo-App mit Signals

Genug Theorie. Lass uns eine kleine Todo-Komponente bauen, die zeigt, wie Signals in der Praxis aussehen.

import { Component, signal, computed } from '@angular/core';
import { FormsModule } from '@angular/forms';

interface Todo {
  id: number;
  text: string;
  done: boolean;
}

@Component({
  selector: 'app-todo',
  standalone: true,
  imports: [FormsModule],
  template: `
    <h2>Meine Todos ({{ openCount() }} offen)</h2>

    <input [(ngModel)]="newTodoText"
           (keyup.enter)="addTodo()"
           placeholder="Neues Todo..." />
    <button (click)="addTodo()">HinzufĂźgen</button>

    <ul>
      @for (todo of filteredTodos(); track todo.id) {
        <li [class.done]="todo.done">
          <input type="checkbox"
                 [checked]="todo.done"
                 (change)="toggleTodo(todo.id)" />
          {{ todo.text }}
          <button (click)="removeTodo(todo.id)">🗑️</button>
        </li>
      }
    </ul>

    <div class="filters">
      <button (click)="filter.set('all')">Alle</button>
      <button (click)="filter.set('open')">Offen</button>
      <button (click)="filter.set('done')">Erledigt</button>
    </div>
  `
})
export class TodoComponent {
  // State als Signals
  todos = signal<Todo[]>([]);
  filter = signal<'all' | 'open' | 'done'>('all');
  newTodoText = '';
  private nextId = 1;

  // Computed: Gefilterte Todos
  filteredTodos = computed(() => {
    const currentFilter = this.filter();
    const allTodos = this.todos();

    switch (currentFilter) {
      case 'open': return allTodos.filter(t => !t.done);
      case 'done': return allTodos.filter(t => t.done);
      default: return allTodos;
    }
  });

  // Computed: Anzahl offener Todos
  openCount = computed(() =>
    this.todos().filter(t => !t.done).length
  );

  addTodo() {
    if (!this.newTodoText.trim()) return;
    this.todos.update(todos => [
      ...todos,
      { id: this.nextId++, text: this.newTodoText.trim(), done: false }
    ]);
    this.newTodoText = '';
  }

  toggleTodo(id: number) {
    this.todos.update(todos =>
      todos.map(t => t.id === id ? { ...t, done: !t.done } : t)
    );
  }

  removeTodo(id: number) {
    this.todos.update(todos => todos.filter(t => t.id !== id));
  }
}

Schau dir an, wie sauber das ist. Kein einziges subscribe(), kein async-Pipe, keine Memory-Leak-Gefahr. Der State lebt in Signals, die abgeleiteten Werte in computed(), und Angular weiß automatisch, was es neu rendern muss.

🚀 linkedSignal() und resource() - Die neuen APIs

Angular entwickelt sich weiter, und mit neueren Versionen kommen zwei spannende APIs dazu, die Signals noch mächtiger machen.

linkedSignal() - Signals die voneinander abhängen

linkedSignal() erstellt ein beschreibbares Signal, das sich automatisch zurßcksetzt, wenn sich eine Quelle ändert. Perfekt fßr "abhängige Defaults".

import { signal, linkedSignal } from '@angular/core';

const products = signal(['Laptop', 'Tablet', 'Phone']);

// Wählt automatisch das erste Produkt, wenn sich die Liste ändert
const selectedProduct = linkedSignal(() => products()[0]);

console.log(selectedProduct()); // "Laptop"

// Manuell änderbar
selectedProduct.set('Tablet');
console.log(selectedProduct()); // "Tablet"

// Wenn sich die Quelle ändert, wird der Wert zurßckgesetzt
products.set(['Monitor', 'Keyboard', 'Mouse']);
console.log(selectedProduct()); // "Monitor"

Der Unterschied zu computed(): Ein linkedSignal ist beschreibbar. Du kannst den Wert manuell ßberschreiben, aber er resettet sich, wenn die Quelle sich ändert. Das ist extrem nßtzlich fßr Dropdown-Selections, Pagination oder Filter, die an eine Datenquelle gebunden sind.

resource() - Asynchrone Daten laden

resource() verbindet Signals mit asynchronen Operationen. Statt HttpClient mit RxJS-Pipes zu nutzen, kannst du Daten direkt signal-basiert laden.

import { signal, resource } from '@angular/core';

const userId = signal(1);

const userResource = resource({
  request: () => ({ id: userId() }),
  loader: async ({ request }) => {
    const response = await fetch(
      `https://api.example.com/users/${request.id}`
    );
    return response.json();
  }
});

// Im Template:
// @if (userResource.isLoading()) {
//   <p>Laden...</p>
// }
// @if (userResource.value()) {
//   <p>{{ userResource.value().name }}</p>
// }
// @if (userResource.error()) {
//   <p>Fehler: {{ userResource.error() }}</p>
// }

// Neuen User laden - triggert automatisch den Loader
userId.set(2);

Das Geniale: resource() gibt dir Loading-State, Error-Handling und automatisches Neuladen geschenkt. Ändert sich userId, wird automatisch ein neuer Request ausgelöst. Kein switchMap nötig.

Angular input() fĂźr Route-Parameter: Schluss mit ActivatedRoute
Mit Angular 16+ kannst du Route-Parameter direkt per input() binden – ganz ohne ActivatedRoute.

🔧 Migration: Von BehaviorSubject zu signal()

Du hast bestehenden Code mit RxJS und willst schrittweise migrieren? Kein Problem. Angular bietet Interop-Funktionen, die den Übergang smooth machen.

Vorher: RxJS-basiert

import { BehaviorSubject, combineLatest, map } from 'rxjs';

@Injectable({ providedIn: 'root' })
export class CartService {
  private items$ = new BehaviorSubject<CartItem[]>([]);
  private discount$ = new BehaviorSubject<number>(0);

  totalPrice$ = combineLatest([this.items$, this.discount$]).pipe(
    map(([items, discount]) => {
      const subtotal = items.reduce((sum, item) => sum + item.price, 0);
      return subtotal * (1 - discount / 100);
    })
  );

  addItem(item: CartItem) {
    this.items$.next([...this.items$.value, item]);
  }

  setDiscount(percent: number) {
    this.discount$.next(percent);
  }
}

Nachher: Signal-basiert

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

@Injectable({ providedIn: 'root' })
export class CartService {
  private items = signal<CartItem[]>([]);
  private discount = signal(0);

  totalPrice = computed(() => {
    const subtotal = this.items().reduce((sum, item) => sum + item.price, 0);
    return subtotal * (1 - this.discount() / 100);
  });

  addItem(item: CartItem) {
    this.items.update(current => [...current, item]);
  }

  setDiscount(percent: number) {
    this.discount.set(percent);
  }
}

Weniger Code. Weniger Boilerplate. Keine Subscriptions, die man vergessen kann zu cleanen. Und das Beste: Du kannst beide Ansätze mischen.

Interop: toSignal() und toObservable()

FĂźr die schrittweise Migration bietet Angular BrĂźcken zwischen beiden Welten:

import { toSignal, toObservable } from '@angular/core/rxjs-interop';
import { HttpClient } from '@angular/common/http';

@Component({...})
export class UserComponent {
  private http = inject(HttpClient);

  // Observable → Signal
  users = toSignal(
    this.http.get<User[]>('/api/users'),
    { initialValue: [] }
  );

  // Signal → Observable (falls du es brauchst)
  searchTerm = signal('');
  searchTerm$ = toObservable(this.searchTerm);
}

toSignal() ist besonders praktisch: Es subscribed automatisch, gibt dir den letzten Wert als Signal zurĂźck und unsubscribed beim Destroy. Der HttpClient bleibt RxJS-basiert, aber in deinem Template arbeitest du mit Signals.

Migrations-Tipps

  • Schritt 1: Fang mit einfachen BehaviorSubject-Feldern in Komponenten an - die sind am einfachsten zu ersetzen.
  • Schritt 2: Ersetze combineLatest + map durch computed() - das ist fast immer ein 1:1-Tausch.
  • Schritt 3: Nutze toSignal() fĂźr HttpClient-Calls, statt manuell zu subscriben.
  • Schritt 4: Lass komplexe RxJS-Streams (WebSockets, Debounce-Chains) erstmal in Ruhe - die sind bei RxJS besser aufgehoben.

⚠️ Häufige Stolperfallen

Bevor du loslegst, ein paar Dinge, die du wissen solltest:

1. Signals sind synchron

Anders als Observables liefern Signals immer sofort einen Wert. Das ist meistens ein Vorteil, aber wenn du auf asynchrone Daten wartest, brauchst du resource() oder toSignal() mit einem initialValue.

2. effect() sparsam einsetzen

Effects sind mächtig, aber sie kÜnnen schnell zu schwer nachvollziehbarem Code fßhren, wenn sie sich gegenseitig triggern. Bevorzuge computed() fßr abgeleitete Werte und nutze effect() nur fßr echte Seiteneffekte (DOM-Manipulation, Logging, API-Calls).

3. Immutability beachten

Signals erkennen Änderungen durch Referenzvergleich. Das heißt: Ein Array mutieren funktioniert nicht - du musst ein neues Array erstellen:

// ❌ Falsch - Signal erkennt die Änderung nicht
const list = signal(['a', 'b']);
list().push('c'); // Nope!

// ✅ Richtig - Neues Array erstellen
list.update(current => [...current, 'c']);

4. Equality-Funktion anpassen

Standardmäßig nutzt Angular Object.is() zum Vergleichen. Für Objekte kannst du eine eigene Equality-Funktion mitgeben:

const user = signal(
  { name: 'Max', age: 30 },
  { equal: (a, b) => a.name === b.name && a.age === b.age }
);
Warum du nur noch TypeScript nutzen solltest
TypeScript bietet dir Typsicherheit, bessere Tooling-UnterstĂźtzung und weniger Bugs.

💡 Fazit

Angular Signals sind kein Hype - sie sind eine durchdachte Ergänzung, die das Framework deutlich zugänglicher macht. Du bekommst reaktives State-Management, das sich wie normales TypeScript anfßhlt, ohne die Lernkurve von RxJS.

Meine Empfehlung:

  • Neue Komponenten → Signals first
  • Bestehender Code → Schrittweise migrieren mit toSignal()
  • Komplexe Streams → RxJS bleibt dein Freund
  • Und vor allem: Keine Angst vor dem Umstieg. Die API ist klein, intuitiv und macht Spaß.

Hast du Fragen oder eigene Erfahrungen mit Angular Signals? Schreib mir gerne in die Kommentare oder per Mail. Ich freue mich über den Austausch! 🚀

Artikel teilen:Share article:

Mehr Artikel entdecken

Traefik + CrowdSec: Securing Your Homelab Against Attacks 🛡️
Vorheriger Artikel

Traefik + CrowdSec: Securing Your Homelab Against Attacks 🛡️

CrowdSec with Traefik in Docker: Automatically protect your homelab against brute-force attacks, scanners, and known malicious IPs.

9 min read • 1. Mai 2026
Angular Signals: Reactive State Management Without RxJS 🚀
Nächster Artikel

Angular Signals: Reactive State Management Without RxJS 🚀

Angular Signals bring reactive state management without RxJS complexity. Learn signal(), computed(), and effect() for cleaner Angular code.

8 min read • 3. Mai 2026
Logging in Angular: Ein mächtiges Werkzeug zur Fehlersuche und Überwachung 🕵️
Ähnlicher Artikel

Logging in Angular: Ein mächtiges Werkzeug zur Fehlersuche und Überwachung 🕵️

Logging ist in Angular unverzichtbar. Von TypeScript-Decorators bis zu strukturierten LoggerServices – so debuggst du effizient.

5 min read • 17. März 2026
Angular und TailwindCSS
Ähnlicher Artikel

Angular und TailwindCSS

Angular und TailwindCSS sind ein starkes Duo. So richtest du Tailwind in deinem Angular-Projekt ein und nutzt Utility-First CSS effektiv.

4 min read • 13. März 2026
Angular input() für Route-Parameter: Schluss mit ActivatedRoute 🚀
Ähnlicher Artikel

Angular input() für Route-Parameter: Schluss mit ActivatedRoute 🚀

Mit Angular 16+ kannst du Route-Parameter direkt per input() in deine Komponente binden – ganz ohne ActivatedRoute. So geht's!

3 min read • 10. März 2026
Angular: Framework für Single Page Applications🌐
Ähnlicher Artikel

Angular: Framework für Single Page Applications🌐

Entdecke Angular, das leistungsstarke Framework von Google, ideal für dynamische Webanwendungen. Erfahre mehr über Komponenten, Datenbindung und Routing! 🌐

9 min read • 10. Aug. 2024