home / skills / avvale / aurora-front / angular-19

angular-19 skill

/.claude/skills/angular-19

This skill helps you implement Angular 19 patterns with signals, standalone components, and resource APIs to modernize reactive UI development.

npx playbooks add skill avvale/aurora-front --skill angular-19

Review the files below or copy the command above to add this skill to your agents.

Files (4)
SKILL.md
6.3 KB
---
name: angular-19
description: >
    Angular 19 patterns: signals, standalone components, resource API, signal
    queries, dependency injection, and Aurora framework integration. Trigger:
    When implementing Angular components, directives, pipes, services, or using
    modern reactive patterns.
license: MIT
metadata:
    author: aurora
    version: '3.0'
    angular_version: '19.x'
    auto_invoke:
        'Angular components, signals, resource API, dependency injection,
        standalone, directives, pipes'
allowed-tools: Read, Edit, Write, Glob, Grep, Bash, WebFetch, WebSearch, Task
---

## When to Use

- Implementing Angular components (detail, list, dialog)
- Working with signals, resources, and reactive patterns
- Creating custom pipes or directives
- Setting up dependency injection
- Configuring change detection strategies

**Reference files** (loaded on demand):

- [signals-api.md](signals-api.md) — Signals, inputs, outputs, model, linkedSignal, signal queries
- [resource-api.md](resource-api.md) — resource(), rxResource(), httpResource()
- [template-syntax.md](template-syntax.md) — @let, @if/@for/@switch, @defer, hydration

---

## Angular 19 Key Changes

### Standalone by Default (BREAKING)

All components, directives, and pipes are standalone by default. No `standalone: true` needed.

```typescript
// ✅ Angular 19: standalone is implicit
@Component({
    selector: 'app-example',
    templateUrl: './example.component.html',
    imports: [CommonModule, MatButtonModule],
})
export class ExampleComponent {}

// ❌ Only if you NEED NgModule (legacy)
@Component({ selector: 'app-legacy', standalone: false })
export class LegacyComponent {}
```

---

## Signals (Stable in v19) — Quick Reference

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

count = signal(0);                                    // Writable
doubleCount = computed(() => this.count() * 2);       // Derived read-only

this.count.set(5);           // Replace
this.count.update(n => n + 1); // Update
const val = this.count();    // Read

// Effect — can set signals directly in v19 (no allowSignalWrites needed)
effect(() => {
    console.log('Count:', this.count());
    this.logCount.set(this.count()); // ✅ allowed in v19
});
```

For full signal API (inputs, outputs, model, linkedSignal, queries) → see [signals-api.md](signals-api.md)

---

## Dependency Injection (Modern)

```typescript
export class MyComponent {
    // ✅ Preferred: inject() function
    private readonly http = inject(HttpClient);
    private readonly router = inject(Router);
    private readonly logger = inject(LoggerService, { optional: true });
    private readonly config = inject(CONFIG_TOKEN, { self: true });
}

// Tree-shakable singleton
@Injectable({ providedIn: 'root' })
export class UserService {}

// ✅ New in v19: provideAppInitializer
providers: [
    provideAppInitializer(() => {
        const config = inject(ConfigService);
        return config.load();
    }),
]
```

---

## RxJS Interop

```typescript
import { toSignal, toObservable } from '@angular/core/rxjs-interop';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';

// Observable → Signal (with custom equality in v19)
arraySignal = toSignal(this.array$, {
    initialValue: [],
    equal: (a, b) => a.length === b.length && a.every((v, i) => v === b[i]),
});

// Signal → Observable
count$ = toObservable(this.count);

// Auto-unsubscribe on destroy
this.data$.pipe(takeUntilDestroyed()).subscribe(data => { /* ... */ });
```

---

## Lifecycle & Rendering (v19)

```typescript
import { afterRenderEffect, afterRender, afterNextRender } from '@angular/core';

// afterRenderEffect — tracks dependencies, reruns when they change
afterRenderEffect(() => {
    const el = this.chartEl().nativeElement;
    this.renderChart(el, this.data());
});

// afterRender — every render cycle
afterRender(() => this.updateScrollPosition());

// afterNextRender — once after next render
afterNextRender(() => this.initializeThirdPartyLib());
```

---

## Pipes & Directives

```typescript
// Pure Pipe (default)
@Pipe({ name: 'dateFormat' })
export class DateFormatPipe implements PipeTransform {
    transform(timestamp: string, format: string): string {
        return dateFromFormat(timestamp, 'YYYY-MM-DD HH:mm:ss').format(format);
    }
}

// Attribute Directive with signal input
@Directive({ selector: '[auFocus]' })
export class FocusDirective {
    private readonly elementRef = inject(ElementRef<HTMLElement>);
    focused = input(true, { alias: 'auFocus', transform: booleanAttribute });

    constructor() {
        effect(() => {
            if (this.focused()) this.elementRef.nativeElement.focus();
        });
    }
}
```

---

## Anti-Patterns

| Avoid                                 | Do Instead                             |
| ------------------------------------- | -------------------------------------- |
| `standalone: true` (redundant in v19) | Omit (standalone by default)           |
| `@Input()` decorator                  | `input()` / `input.required()`         |
| `@Output()` decorator                 | `output()`                             |
| `@ViewChild()` decorator              | `viewChild()` / `viewChild.required()` |
| `allowSignalWrites` in effect         | Not needed in v19                      |
| Manual subscription cleanup           | `takeUntilDestroyed()`                 |
| `ChangeDetectionStrategy.Default`     | Use `OnPush` with signals              |
| `ngOnInit` for async data             | `resource()` / `rxResource()`          |
| Constructor injection (verbose)       | `inject()` function                    |
| `APP_INITIALIZER` token               | `provideAppInitializer()`              |

---

## Related Skills

| Skill              | When to Use Together                       |
| ------------------ | ------------------------------------------ |
| `angular-material` | Material components, CDK, theming          |
| `tailwind`         | Styling with Tailwind CSS                  |
| `typescript`       | TypeScript patterns, generics, type safety |
| `aurora-schema`    | When working with Aurora YAML schemas      |

---

## Resources

- [Angular 19 Official Blog](https://blog.angular.dev/meet-angular-v19-7b29dfd05b84)
- [Angular Signals Guide](https://angular.dev/guide/signals)
- [Resource API Guide](https://angular.dev/guide/signals/resource)

Overview

This skill covers practical Angular 19 patterns: signals, standalone components, resource APIs, signal queries, dependency injection, and integration with the Aurora framework. It focuses on modern reactive practices, tree-shakable providers, lifecycle hooks, and RxJS interop to build efficient, predictable UI. Use it when implementing components, directives, pipes, services, or migrating to Angular 19 idioms.

How this skill works

The skill inspects component and service implementations and recommends Angular 19 idiomatic replacements (signals, input/output helpers, and view queries). It highlights lifecycle and rendering utilities (afterRenderEffect, afterRender, afterNextRender), resource and rxResource usage for async data, and rxjs interop helpers like toSignal/toObservable and takeUntilDestroyed. It also suggests DI patterns using inject(), providedIn singletons, and provideAppInitializer for app startup tasks.

When to use it

  • Implementing new or migrated Angular components, directives, pipes, or services
  • Adopting reactive state with signals, computed, and effect in component logic
  • Loading async data with resource(), rxResource(), or signal-based queries
  • Rewriting DI to use inject(), provideAppInitializer(), and tree-shakable providers
  • Integrating third-party libs or Aurora framework features that need post-render initialization

Best practices

  • Prefer standalone components by default; avoid explicit standalone: true
  • Use signal(), computed(), and effect() for local reactive state; replace @Input/@Output with input()/output() helpers
  • Use resource()/rxResource() for async data and avoid ngOnInit for data loading
  • Use inject() inside classes rather than constructor injection for brevity and tree shaking
  • Adopt OnPush change detection with signals and use takeUntilDestroyed() for RxJS cleanup

Example use cases

  • Detail view component using signals for local model, computed values for derived state, and resource() for remote loading
  • Directive that exposes a signal input and focuses an element when the signal becomes true
  • Service providedIn: 'root' that exposes a signal-backed API and converts observables with toSignal
  • App initializer using provideAppInitializer() to load configuration before bootstrap
  • Component that uses afterRenderEffect to sync a chart library with signal-driven data

FAQ

Should I still use NgModule and standalone: true?

No. Angular 19 assumes standalone by default; only add NgModule if you need legacy module behavior.

When should I use resource() vs rxResource()?

Use resource() for simple signal-backed async reads and rxResource() when you need Observable-based patterns or more advanced RxJS control.