Skip to main content

angular-signals

Use when building Angular 16+ applications requiring fine-grained reactive state management and zone-less change detection.

Source facts

Repository
TheBushidoCollective/han
Last source activity
April 28, 2026 at 03:37
Detected SKILL.md language
English
Stars
198
Forks
21

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
angular-signals
user-invocable
false
description
Use when building Angular 16+ applications requiring fine-grained reactive state management and zone-less change detection.
allowed-tools
["Bash","Read"]
# Angular Signals Master Angular Signals for building reactive applications with fine-grained reactivity and improved performance. ## Signal Basics ### Creating and Using Signals ```typescript import { Component, signal, computed, effect } from '@angular/core'; @Component({ selector: 'app-counter', standalone: true, template: ` <div> <p>Count: {{ count() }}</p> <p>Double: {{ doubleCount() }}</p> <button (click)="increment()">+</button> <button (click)="decrement()">-</button> <button (click)="reset()">Reset</button> </div> ` }) export class CounterComponent { // Writable signal count = signal(0); // Computed signal doubleCount = computed(() => this.count() * 2); constructor() { // Effect runs when count changes effect(() => { console.log(`Count is: ${this.count()}`); }); } increment() { this.count.update(value => value + 1); } decrement() { this.count.update(value => value - 1); } reset() { this.count.set(0); } } ``` ### Signal Methods ```typescript import { signal } from '@angular/core'; // Create signal const count = signal(0); // set - replace value count.set(5); // update - transform current value count.update(value => value + 1); // mutate - modify object (experimental) const user = signal({ name: 'John', age: 30 }); user.mutate(value => { value.age = 31; // Mutate in place }); // Read value const current = count(); // Call as function ``` ## Computed Signals ### Basic Computed ```typescript import { signal, computed } from '@angular/core'; const firstName = signal('John'); const lastName = signal('Doe'); // Computed signal const fullName = computed(() => { return `${firstName()} ${lastName()}`; }); console.log(fullName()); // John Doe firstName.set('Jane'); console.log(fullName()); // Jane Doe (automatically updates) ``` ### Complex Computed ```typescript interface Product { id: number; name: string; price: number; quantity: number; } @Component({ selector: 'app-cart' }) export class CartComponent { items = signal<Product[]>([]); // Computed: total items itemCount = computed(() => { return this.items().reduce((sum, item) => sum + item.quantity, 0); }); // Computed: subtotal subtotal = computed(() => { return this.items().reduce((sum, item) => sum + (item.price * item.quantity), 0 ); }); // Computed: tax tax = computed(() => this.subtotal() * 0.08); // Computed: total total = computed(() => this.subtotal() + this.tax()); // Computed: formatted total formattedTotal = computed(() => { return `$${this.total().toFixed(2)}`; }); } ``` ### Chained Computed ```typescript const count = signal(1); const doubled = computed(() => count() * 2); const quadrupled = computed(() => doubled() * 2); const formatted = computed(() => `Count: ${quadrupled()}`); console.log(formatted()); // Count: 4 count.set(2); console.log(formatted()); // Count: 8 ``` ## Effects ### Basic Effects ```typescript import { Component, signal, effect } from '@angular/core'; @Component({ selector: 'app-logger' }) export class LoggerComponent { count = signal(0); constructor() { // Effect runs when count changes effect(() => { console.log(`Count changed to: ${this.count()}`); }); } increment() { this.count.update(v => v + 1); // Triggers effect } } ``` ### Effect Cleanup ```typescript import { effect } from '@angular/core'; const count = signal(0); effect((onCleanup) => { const timer = setInterval(() => { console.log(count()); }, 1000); // Cleanup function onCleanup(() => { clearInterval(timer); }); }); ``` ### Conditional Effects ```typescript import { effect, signal } from '@angular/core'; const enabled = signal(true); const count = signal(0); effect(() => { // Only run if enabled if (!enabled()) return; console.log(`Count: ${count()}`); }); ``` ## Signal Inputs ### Component Inputs as Signals ```typescript import { Component, input, computed } from '@angular/core'; @Component({ selector: 'app-user-profile', standalone: true, template: ` <div> <h2>{{ displayName() }}</h2> <p>Age: {{ age() }}</p> <p>Is adult: {{ isAdult() }}</p> </div> ` }) export class UserProfileComponent { // Signal inputs (Angular 17.1+) firstName = input.required<string>(); lastName = input.required<string>(); age = input(0); // Optional with default // Computed from inputs displayName = computed(() => `${this.firstName()} ${this.lastName()}` ); isAdult = computed(() => this.age() >= 18); } // Usage <app-user-profile [firstName]="'John'" [lastName]="'Doe'" [age]="30" /> ``` ### Transform Input Signals ```typescript import { Component, input } from '@angular/core'; @Component({ selector: 'app-formatted-text' }) export class FormattedTextComponent { // Transform input text = input('', { transform: (value: string) => value.toUpperCase() }); // Alias input label = input('', { alias: 'labelText' }); } // Usage <app-formatted-text [text]="'hello'" [labelText]="'Name'" /> ``` ## Signal Outputs ### Component Outputs as Signals ```typescript import { Component, output } from '@angular/core'; @Component({ selector: 'app-button', template: ` <button (click)="handleClick()"> {{ label() }} </button> ` }) export class ButtonComponent { label = input('Click me'); // Signal output (Angular 17.1+) clicked = output<void>(); valueChanged = output<number>(); private clickCount = signal(0); handleClick() { this.clickCount.update(v => v + 1); this.clicked.emit(); this.valueChanged.emit(this.clickCount()); } } // Usage <app-button (clicked)="onClicked()" (valueChanged)="onValueChanged($event)" /> ``` ## Signal Queries ### ViewChild with Signals ```typescript import { Component, viewChild, ElementRef, afterNextRender } from '@angular/core'; @Component({ selector: 'app-input-focus', template: ` <input #inputElement type="text" /> <button (click)="focusInput()">Focus</button> ` }) export class InputFocusComponent { // Signal-based viewChild inputElement = viewChild<ElementRef>('inputElement'); constructor() { afterNextRender(() => { // Access element after render const element = this.inputElement()?.nativeElement; if (element) { element.focus(); } }); } focusInput() { this.inputElement()?.nativeElement.focus(); } } ``` ### ViewChildren with Signals ```typescript import { Component, viewChildren, ElementRef } from '@angular/core'; @Component({ selector: 'app-list', standalone: true, template: ` @for (item of items(); track item) { <div #item>{{ item }}</div> } <p>Item count: {{ itemElements().length }}</p> ` }) export class ListComponent { items = signal(['A', 'B', 'C']); // Signal-based viewChildren itemElements = viewChildren<ElementRef>('item'); logItemCount() { console.log(`Count: ${this.itemElements().length}`); } } ``` ### ContentChild with Signals ```typescript import { Component, contentChild, Directive } from '@angular/core'; @Directive({ selector: '[appHeader]' }) export class HeaderDirective {} @Component({ selector: 'app-card', standalone: true, template: ` <div class="card"> <ng-content select="[appHeader]" /> <ng-content /> @if (hasHeader()) { <p>Has custom header</p> } </div> ` }) export class CardComponent { // Signal-based contentChild header = contentChild(HeaderDirective); hasHeader = computed(() => !!this.header()); } // Usage <app-card> <h2 appHeader>Title</h2> <p>Content</p> </app-card> ``` ## Signals vs Observables ### When to Use Signals ```typescript // Use signals for synchronous state @Component({ selector: 'app-counter' }) export class CounterComponent { count = signal(0); // Signal for synchronous state increment() { this.count.update(v => v + 1); } } ``` ### When to Use Observables ```typescript // Use observables for async operations @Component({ selector: 'app-user-list' }) export class UserListComponent { private http = inject(HttpClient);
View on GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub