Claude
Skills
Sign in
Back

angular-signals

Included with Lifetime
$97 forever

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

General

What this skill does


# 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

 

Related in General