---
name: angular-core-23f1f7c3c03e
description: >
  Angular core patterns: standalone components, signals, inject, control flow, zoneless.
  Trigger: When creating Angular components, using signals, or setting up zoneless.
metadata:
  author: gentleman-programming
  version: "1.0"
---

## Template File (REQUIRED for production code)

**NEVER inline the template in the TypeScript class.** Always use a separate `.html` file.

```typescript
// ✅ ALWAYS - External template
@Component({
  selector: 'app-user',
  imports: [CommonModule],
  changeDetection: ChangeDetectionStrategy.OnPush,
  templateUrl: './user.component.html',
})
export class UserComponent {}
```

```html
<!-- user.component.html -->
<h2>{{ displayName() }}</h2>
```

```typescript
// ❌ NEVER - Inlined template
@Component({
  selector: 'app-user',
  template: `<h2>{{ displayName() }}</h2>`
})
export class UserComponent {}
```

**This is non-negotiable.** Template logic belongs in its own file, not mixed with the component class.

> **Scope:** This rule applies to production application code. Inline templates in documentation snippets and skill example code are acceptable for brevity and readability.

---

## Standalone Components (REQUIRED)

---

## Input/Output Functions (REQUIRED)

```typescript
// ✅ ALWAYS: Function-based
readonly user = input.required<User>();
readonly disabled = input(false);
readonly selected = output<User>();
readonly checked = model(false);  // Two-way binding

// ❌ NEVER: Decorators
@Input() user: User;
@Output() selected = new EventEmitter<User>();
```

---

## Signals for State (REQUIRED)

```typescript
readonly count = signal(0);
readonly doubled = computed(() => this.count() * 2);

// Update
this.count.set(5);
this.count.update(prev => prev + 1);

// Side effects
effect(() => localStorage.setItem('count', this.count().toString()));
```

---

## Prefer Signals Over Reactive Lifecycle Hooks

Use signals, `effect()`, and `computed()` for reactive component logic. `ngOnInit()` remains valid for imperative initialization, and `ngOnDestroy()` remains valid for cleanup. Avoid `ngAfterViewInit()` and `ngOnChanges()` when signals can express the same behavior more clearly.

```typescript
// ✅ Valid: imperative initialization
ngOnInit() {
  this.loadInitialUser();
}

// ❌ Avoid when a signal effect can express the same reactive behavior
ngOnChanges(changes: SimpleChanges) {
  if (changes['userId']) {
    this.loadUser(changes['userId'].currentValue);
  }
}

// ✅ Preferred: Signals + effect for reactive updates
readonly userId = input.required<string>();
readonly user = signal<User | null>(null);

private readonly userEffect = effect(() => {
  // Runs automatically when userId() changes
  this.loadUser(this.userId());
});

// ✅ For derived data, use computed
readonly displayName = computed(() => this.user()?.name ?? 'Guest');
```

### When to Use What

| Need | Use |
|------|-----|
| Imperative initialization | `ngOnInit()` |
| React to input changes | `effect()` watching the input signal |
| Derived/computed state | `computed()` |
| Side effects (API calls, localStorage) | `effect()` |
| Cleanup on destroy | `ngOnDestroy()` or `DestroyRef` + `inject()` |

```typescript
// Cleanup example
private readonly destroyRef = inject(DestroyRef);

constructor() {
  const subscription = someObservable$.subscribe();
  this.destroyRef.onDestroy(() => subscription.unsubscribe());
}
```

---

## inject() Over Constructor (REQUIRED)

```typescript
// ✅ ALWAYS
private readonly http = inject(HttpClient);

// ❌ NEVER
constructor(private http: HttpClient) {}
```

---

## Native Control Flow (REQUIRED)

```html
@if (loading()) {
  <spinner />
} @else {
  @for (item of items(); track item.id) {
    <item-card [data]="item" />
  } @empty {
    <p>No items</p>
  }
}

@switch (status()) {
  @case ('active') { <span>Active</span> }
  @default { <span>Unknown</span> }
}
```

---

## RxJS - Only When Needed

Signals are the default. Use RxJS ONLY for complex async operations.

| Use Signals | Use RxJS |
|-------------|----------|
| Component state | Combining multiple streams |
| Derived values | Debounce/throttle |
| Simple async (single API call) | Race conditions |
| Input/Output | WebSockets, real-time |
| | Complex error retry logic |

```typescript
// ✅ Simple API call - use signals
readonly user = signal<User | null>(null);
readonly loading = signal(false);

async loadUser(id: string) {
  this.loading.set(true);
  this.user.set(await firstValueFrom(this.http.get<User>(`/api/users/${id}`)));
  this.loading.set(false);
}

// ✅ Complex stream - use RxJS
readonly searchResults$ = this.searchTerm$.pipe(
  debounceTime(300),
  distinctUntilChanged(),
  switchMap(term => this.http.get<Results>(`/api/search?q=${term}`))
);

// Convert to signal when needed in template
readonly searchResults = toSignal(this.searchResults$, { initialValue: [] });
```

---

## Zoneless Angular (REQUIRED)

Angular is zoneless. Use `provideZonelessChangeDetection()`.

```typescript
bootstrapApplication(AppComponent, {
  providers: [provideZonelessChangeDetection()]
});
```

Remove ZoneJS:
```bash
npm uninstall zone.js
```

Remove from `angular.json` polyfills: `zone.js` and `zone.js/testing`.

### Zoneless Requirements
- Use `OnPush` change detection
- Use signals for state (auto-notifies Angular)
- Use `AsyncPipe` for observables
- Use `markForCheck()` when needed

---

## Resources

- https://angular.dev/guide/signals
- https://angular.dev/guide/templates/control-flow
- https://angular.dev/guide/zoneless

## Imported Angular Signals Reference Pack

Additional Angular signal patterns were imported under `references/frontend-pack-angular-signals/`:

- `overview.md` — `signal`, `computed`, `linkedSignal`, `effect`, RxJS interop
- `references/signal-patterns.md` — component/service state patterns and edge cases

Use these as supporting references. Prefer the canonical `angular-core` guidance when conflicts appear.



---
Source attribution: CamiloAndresGTRUniandes
Original project: https://github.com/CamiloAndresGTRUniandes/lucy-ai/blob/52e3d6d9faf7773a4a764475b8f5a92a72e39749/skills/angular-core/SKILL.md
License: MIT
License information: https://opensource.org/license/mit
Original upstream instructions; downloaded from Dave AI.

## Upstream license notice

MIT License

Copyright (c) 2026 Camilo Andres / lucy-ai

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.



# Dave AI resource metadata

Creator / publisher: CamiloAndresGTRUniandes
Original project: https://github.com/CamiloAndresGTRUniandes/lucy-ai/blob/52e3d6d9faf7773a4a764475b8f5a92a72e39749/skills/angular-core/SKILL.md
Attribution basis: Source repository record

Fields of work: Development & Engineering, Product Management
Roles: Frontend Developer, Product Manager
Specialties: Frontend
Tools: Tool not specified
Country applicability: Country not specified
Classification basis: Source catalog labels
Instruction SHA-256: 23a13fda9760e27f1d3a9c6fcd9cc05eeb78b8835e9adc4c33fe7a3a920b5f02
