Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
analogjs avatar

Angular Signals

  • 7.6k installs
  • 594 repo stars
  • Updated March 23, 2026
  • analogjs/angular-skills

angular-signals is an agent skill that implements Angular v20+ signal-based reactive state using signal, computed, linkedSignal, effect, and RxJS interop helpers.

About

The angular-signals skill teaches signal-based reactive state management for Angular v20 and later. Agents guide creation of writable state with signal, derived state with computed, dependent resets with linkedSignal, and side effects with effect inside injection contexts including cleanup handlers. Examples cover component state patterns such as filtered todos, service stores with readonly public signals, custom equality functions, and untracked reads. RxJS interop sections document toSignal for observables and HTTP streams plus toObservable with debounce and switchMap for search. The skill contrasts synchronous fine-grained reactivity against BehaviorSubject patterns and shows auth service state with computed isAuthenticated. Advanced resource patterns are referenced in signal-patterns.md. Triggers include state management questions, migrations from BehaviorSubject or Observable patterns, and reactive data flow implementation. Use when building Angular components or services that need computed derivatives, linked selections, or observable bridges.

  • Documents signal, computed, linkedSignal, and effect APIs with Angular v20+ examples.
  • Component todo-list pattern with filtered derived state and action methods.
  • RxJS interop via toSignal and toObservable with debounced HTTP search.
  • Service pattern with private writable signals and public readonly accessors.
  • Custom equality and untracked reads for performance-sensitive computed chains.

Angular Signals by the numbers

  • 7,646 all-time installs (skills.sh)
  • +135 installs in the week ending Aug 5, 2026 (Skillselion tracking)
  • Ranked #70 of 2,245 Frontend Development skills by installs in the Skillselion catalog
  • Security screen: LOW risk (skills.sh audit)
  • Data as of Aug 5, 2026 (Skillselion catalog sync)
At a glance

angular-signals capabilities & compatibility

Capabilities
writable signal and computed derived state patte · linkedsignal dependent reset with previous value · effect side effects with oncleanup in injection · tosignal and toobservable rxjs interop examples · service store pattern with readonly public signa
Use cases
frontend · refactoring · documentation
From the docs

What angular-signals says it does

Implement signal-based reactive state management in Angular v20+.
SKILL.md
Signals are Angular's reactive primitive for state management. They provide synchronous, fine-grained reactivity.
SKILL.md
npx skills add https://github.com/analogjs/angular-skills --skill angular-signals

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs7.6k
repo stars594
Security audit3 / 3 scanners passed
Last updatedMarch 23, 2026
Repositoryanalogjs/angular-skills

How do I replace BehaviorSubject/Observable state with Angular signals for synchronous fine-grained reactivity?

Implement Angular v20+ signal-based state with signal, computed, linkedSignal, effect, and RxJS interop patterns.

Who is it for?

Angular developers on v20+ converting state management to signals or implementing reactive component data flows.

Skip if: Skip for non-Angular frameworks or teams staying entirely on RxJS without signal migration.

When should I use this skill?

User asks about Angular signals, converting from BehaviorSubject, or implementing reactive state with computed and effect.

What you get

Working signal, computed, linkedSignal, and effect patterns in components and services plus RxJS bridge examples for HTTP and search.

  • signal-based components
  • resource loaders
  • signal test examples

By the numbers

  • Documents five signal pattern areas including Resource API, stores, forms, async, and testing

Files

SKILL.mdMarkdownGitHub ↗

Angular Signals

Signals are Angular's reactive primitive for state management. They provide synchronous, fine-grained reactivity.

Core Signal APIs

signal() - Writable State

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

// Create writable signal
const count = signal(0);

// Read value
console.log(count()); // 0

// Set new value
count.set(5);

// Update based on current value
count.update(c => c + 1);

// With explicit type
const user = signal<User | null>(null);
user.set({ id: 1, name: 'Alice' });

computed() - Derived State

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

const firstName = signal('John');
const lastName = signal('Doe');

// Derived signal - automatically updates when dependencies change
const fullName = computed(() => `${firstName()} ${lastName()}`);

console.log(fullName()); // "John Doe"
firstName.set('Jane');
console.log(fullName()); // "Jane Doe"

// Computed with complex logic
const items = signal<Item[]>([]);
const filter = signal('');

const filteredItems = computed(() => {
  const query = filter().toLowerCase();
  return items().filter(item => 
    item.name.toLowerCase().includes(query)
  );
});

const totalPrice = computed(() => 
  filteredItems().reduce((sum, item) => sum + item.price, 0)
);

linkedSignal() - Dependent State with Reset

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

const options = signal(['A', 'B', 'C']);

// Resets to first option when options change
const selected = linkedSignal(() => options()[0]);

console.log(selected()); // "A"
selected.set('B');       // User selects B
console.log(selected()); // "B"
options.set(['X', 'Y']); // Options change
console.log(selected()); // "X" - auto-reset to first

// With previous value access
const items = signal<Item[]>([]);

const selectedItem = linkedSignal<Item[], Item | null>({
  source: () => items(),
  computation: (newItems, previous) => {
    // Try to preserve selection if item still exists
    const prevItem = previous?.value;
    if (prevItem && newItems.some(i => i.id === prevItem.id)) {
      return prevItem;
    }
    return newItems[0] ?? null;
  },
});

effect() - Side Effects

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

@Component({...})
export class Search {
  query = signal('');
  
  constructor() {
    // Effect runs when query changes
    effect(() => {
      console.log('Search query:', this.query());
    });
    
    // Effect with cleanup
    effect((onCleanup) => {
      const timer = setInterval(() => {
        console.log('Current query:', this.query());
      }, 1000);
      
      onCleanup(() => clearInterval(timer));
    });
  }
}

Effect rules:

  • Run in injection context (constructor or with runInInjectionContext)
  • Automatically cleaned up when component destroys

Component State Pattern

@Component({
  selector: 'app-todo-list',
  template: `
    <input [value]="newTodo()" (input)="newTodo.set($any($event.target).value)" />
    <button (click)="addTodo()" [disabled]="!canAdd()">Add</button>
    
    <ul>
      @for (todo of filteredTodos(); track todo.id) {
        <li [class.done]="todo.done">
          {{ todo.text }}
          <button (click)="toggleTodo(todo.id)">Toggle</button>
        </li>
      }
    </ul>
    
    <p>{{ remaining() }} remaining</p>
  `,
})
export class TodoList {
  // State
  todos = signal<Todo[]>([]);
  newTodo = signal('');
  filter = signal<'all' | 'active' | 'done'>('all');
  
  // Derived state
  canAdd = computed(() => this.newTodo().trim().length > 0);
  
  filteredTodos = computed(() => {
    const todos = this.todos();
    switch (this.filter()) {
      case 'active': return todos.filter(t => !t.done);
      case 'done': return todos.filter(t => t.done);
      default: return todos;
    }
  });
  
  remaining = computed(() => 
    this.todos().filter(t => !t.done).length
  );
  
  // Actions
  addTodo() {
    const text = this.newTodo().trim();
    if (text) {
      this.todos.update(todos => [
        ...todos,
        { id: crypto.randomUUID(), text, done: false }
      ]);
      this.newTodo.set('');
    }
  }
  
  toggleTodo(id: string) {
    this.todos.update(todos =>
      todos.map(t => t.id === id ? { ...t, done: !t.done } : t)
    );
  }
}

RxJS Interop

toSignal() - Observable to Signal

import { toSignal } from '@angular/core/rxjs-interop';
import { interval } from 'rxjs';

@Component({...})
export class Timer {
  private http = inject(HttpClient);
  
  // From observable - requires initial value or allowUndefined
  counter = toSignal(interval(1000), { initialValue: 0 });
  
  // From HTTP - undefined until loaded
  users = toSignal(this.http.get<User[]>('/api/users'));
  
  // With requireSync for synchronous observables (BehaviorSubject)
  private user$ = new BehaviorSubject<User | null>(null);
  currentUser = toSignal(this.user$, { requireSync: true });
}

toObservable() - Signal to Observable

import { toObservable } from '@angular/core/rxjs-interop';
import { switchMap, debounceTime } from 'rxjs';

@Component({...})
export class Search {
  query = signal('');
  
  private http = inject(HttpClient);
  
  // Convert signal to observable for RxJS operators
  results = toSignal(
    toObservable(this.query).pipe(
      debounceTime(300),
      switchMap(q => this.http.get<Result[]>(`/api/search?q=${q}`))
    ),
    { initialValue: [] }
  );
}

Signal Equality

// Custom equality function
const user = signal<User>(
  { id: 1, name: 'Alice' },
  { equal: (a, b) => a.id === b.id }
);

// Only triggers updates when ID changes
user.set({ id: 1, name: 'Alice Updated' }); // No update
user.set({ id: 2, name: 'Bob' }); // Triggers update

Untracked Reads

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

const a = signal(1);
const b = signal(2);

// Only depends on 'a', not 'b'
const result = computed(() => {
  const aVal = a();
  const bVal = untracked(() => b());
  return aVal + bVal;
});

Service State Pattern

@Injectable({ providedIn: 'root' })
export class Auth {
  // Private writable state
  private _user = signal<User | null>(null);
  private _loading = signal(false);
  
  // Public read-only signals
  readonly user = this._user.asReadonly();
  readonly loading = this._loading.asReadonly();
  readonly isAuthenticated = computed(() => this._user() !== null);
  
  private http = inject(HttpClient);
  
  async login(credentials: Credentials): Promise<void> {
    this._loading.set(true);
    try {
      const user = await firstValueFrom(
        this.http.post<User>('/api/login', credentials)
      );
      this._user.set(user);
    } finally {
      this._loading.set(false);
    }
  }
  
  logout(): void {
    this._user.set(null);
  }
}

For advanced patterns including resource(), see references/signal-patterns.md.

Related skills

FAQ

Which Angular version does angular-signals target?

Angular v20+ with the core signal, computed, linkedSignal, and effect APIs.

How do signals integrate with RxJS?

Use toSignal for observables and toObservable to pipe signal changes through operators like debounceTime and switchMap.

Where can effects run?

Inside an injection context such as a component constructor, with automatic cleanup on destroy.

Is Angular Signals safe to install?

skills.sh reports 3 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.