Compare commits

..

19 Commits

Author SHA1 Message Date
sck_0
ac20cc63b6 chore(release): v4.8.0 - Computer Vision & Angular 2026-02-04 08:52:08 +01:00
github-actions[bot]
e1c84cd8f4 chore: sync generated registry files [ci skip] 2026-02-04 07:49:25 +00:00
sickn33
73e51321ca Merge pull request #60 from chauey/main
feat: add angular
2026-02-04 08:49:08 +01:00
sickn33
eca46228ed Merge pull request #58 from PabloSMD/main
feat(skills): add computer-vision-expert (SOTA 2026: YOLO26, SAM 3)
2026-02-04 08:48:51 +01:00
Chau (Joe) Nguyen
aa164fac16 fix(angular): Add risk and source fields to meet quality bar requirements 2026-02-04 01:25:24 -06:00
Chau (Joe) Nguyen
6247fcefab fix(angular): Clean up formatting and fix JSX example in README
- Remove duplicate horizontal rules in angular/SKILL.md
- Remove duplicate horizontal rules in angular-best-practices/SKILL.md
- Fix React-style JSX in angular-ui-patterns/README.md to use Angular template syntax
2026-02-04 00:51:34 -06:00
Chau (Joe) Nguyen
b46e45fb4d feat(angular): Add metadata.json and README.md to all Angular skills
- Add metadata.json with version tracking, organization, and references
- Add README.md with skill overviews, usage guides, and quick references
- Brings Angular skills to parity with React Best Practices infrastructure
- Covers: angular, angular-state-management, angular-ui-patterns, angular-best-practices
2026-02-04 00:48:55 -06:00
github-actions[bot]
8839ed1b2d chore: update star history chart 2026-02-04 06:47:07 +00:00
Chau (Joe) Nguyen
5ba1fe9a97 feat(skills): enhance Angular skills with Composition and Async Waterfalls
- Add Component Composition & Reusability section to angular/SKILL.md
  - Content Projection with ng-content and select
  - Host Directives for behavior composition

- Add Async Operations & Waterfalls section to angular-best-practices/SKILL.md
  - Parallel execution with forkJoin
  - Flattening with switchMap
  - SSR waterfall prevention with resolvers
2026-02-04 00:35:31 -06:00
Chau (Joe) Nguyen
85f26eb186 feat: Introduce new skill documentation for Angular UI patterns, general Angular, best practices, and state management. 2026-02-03 21:33:08 -06:00
Pablo
0fc520c7fe feat(skills): add computer-vision-expert (SOTA 2026: YOLO26, SAM 3) 2026-02-03 17:16:04 -03:00
github-actions[bot]
7f5ca000bd chore: sync generated registry files [ci skip] 2026-02-03 18:45:56 +00:00
sck_0
679eb72d23 docs: add ASK Supported badge (ref #56) 2026-02-03 19:45:38 +01:00
github-actions[bot]
2b3277c066 chore: sync generated registry files [ci skip] 2026-02-03 18:38:42 +00:00
sck_0
850c940dfd chore: sync generated files 2026-02-03 19:38:23 +01:00
sck_0
1bc750e4a1 fix: obfuscate regex to prevent css build error (fixes #54) 2026-02-03 19:38:22 +01:00
github-actions[bot]
84a41851e0 chore: sync generated registry files [ci skip] 2026-02-03 18:33:36 +00:00
sck_0
6d94cf984c chore: sync generated files 2026-02-03 19:33:01 +01:00
liyin2015
129949ddf0 fix: correct AdaL CLI information
Fix several inaccuracies in AdaL entry:
- Type: Agent → CLI (AdaL is a CLI tool like Claude Code)
- Badge: HumanSignal/Adala → SylphAI (correct company)
- Description: Self-evolving AI Agent → Self-evolving Coding Agent
- Invocation: Auto (skills load on-demand when relevant)
- Path: .agent/skills/ → .adal/skills/

AdaL CLI is fully compatible with Claude Code skills and uses the same
slash command pattern for skill invocation.

Ref: https://docs.sylph.ai/docs/features/plugins-and-skills

Co-Authored-By: AdaL <adal@sylph.ai>
2026-02-03 10:27:27 -08:00
16 changed files with 2823 additions and 30 deletions

View File

@@ -1,13 +1,15 @@
# Skill Catalog
Generated at: 2026-02-03T09:20:12.539Z
Generated at: 2026-02-04T07:52:07.897Z
Total skills: 626
Total skills: 631
## architecture (60)
## architecture (62)
| Skill | Description | Tags | Triggers |
| --- | --- | --- | --- |
| `angular` | Modern Angular (v20+) expert with deep knowledge of Signals, Standalone Components, Zoneless applications, SSR/Hydration, and reactive patterns. Use PROACTIV... | angular | angular, v20, deep, knowledge, signals, standalone, components, zoneless, applications, ssr, hydration, reactive |
| `angular-state-management` | Master modern Angular state management with Signals, NgRx, and RxJS. Use when setting up global state, managing component stores, choosing between state solu... | angular, state | angular, state, signals, ngrx, rxjs, setting, up, global, managing, component, stores, choosing |
| `architect-review` | Master software architect specializing in modern architecture patterns, clean architecture, microservices, event-driven systems, and DDD. Reviews system desi... | | architect, review, software, specializing, architecture, clean, microservices, event, driven, ddd, reviews, designs |
| `architecture` | Architectural decision-making framework. Requirements analysis, trade-off evaluation, ADR documentation. Use when making architecture decisions or analyzing ... | architecture | architecture, architectural, decision, making, framework, requirements, analysis, trade, off, evaluation, adr, documentation |
| `architecture-decision-records` | Write and maintain Architecture Decision Records (ADRs) following best practices for technical decision documentation. Use when documenting significant techn... | architecture, decision, records | architecture, decision, records, write, maintain, adrs, following, technical, documentation, documenting, significant, decisions |
@@ -111,7 +113,7 @@ Total skills: 626
| `startup-financial-modeling` | This skill should be used when the user asks to "create financial projections", "build a financial model", "forecast revenue", "calculate burn rate", "estima... | startup, financial, modeling | startup, financial, modeling, skill, should, used, user, asks, projections, model, forecast, revenue |
| `team-composition-analysis` | This skill should be used when the user asks to "plan team structure", "determine hiring needs", "design org chart", "calculate compensation", "plan equity a... | team, composition | team, composition, analysis, skill, should, used, user, asks, plan, structure, determine, hiring |
## data-ai (92)
## data-ai (93)
| Skill | Description | Tags | Triggers |
| --- | --- | --- | --- |
@@ -121,6 +123,7 @@ Total skills: 626
| `ai-engineer` | Build production-ready LLM applications, advanced RAG systems, and intelligent agents. Implements vector search, multimodal AI, agent orchestration, and ente... | ai | ai, engineer, llm, applications, rag, intelligent, agents, implements, vector, search, multimodal, agent |
| `ai-wrapper-product` | Expert in building products that wrap AI APIs (OpenAI, Anthropic, etc.) into focused tools people will pay for. Not just 'ChatGPT but different' - products t... | ai, wrapper, product | ai, wrapper, product, building, products, wrap, apis, openai, anthropic, etc, people, pay |
| `analytics-tracking` | Design, audit, and improve analytics tracking systems that produce reliable, decision-ready data. Use when the user wants to set up, fix, or evaluate analyti... | analytics, tracking | analytics, tracking, audit, improve, produce, reliable, decision, data, user, wants, set, up |
| `angular-ui-patterns` | Modern Angular UI patterns for loading states, error handling, and data display. Use when building UI components, handling async data, or managing component ... | angular, ui | angular, ui, loading, states, error, handling, data, display, building, components, async, managing |
| `api-documenter` | Master API documentation with OpenAPI 3.1, AI-powered tools, and modern developer experience practices. Create interactive docs, generate SDKs, and build com... | api, documenter | api, documenter, documentation, openapi, ai, powered, developer, experience, interactive, docs, generate, sdks |
| `autonomous-agent-patterns` | Design patterns for building autonomous coding agents. Covers tool integration, permission systems, browser automation, and human-in-the-loop workflows. Use ... | autonomous, agent | autonomous, agent, building, coding, agents, covers, integration, permission, browser, automation, human, loop |
| `autonomous-agents` | Autonomous agents are AI systems that can independently decompose goals, plan actions, execute tools, and self-correct without constant human guidance. The c... | autonomous, agents | autonomous, agents, ai, independently, decompose, goals, plan, actions, execute, self, correct, without |
@@ -295,13 +298,14 @@ TRIGGER: "shopify", "shopify app", "checkout extension",... | shopify | shopify,
| `viral-generator-builder` | Expert in building shareable generator tools that go viral - name generators, quiz makers, avatar creators, personality tests, and calculator tools. Covers t... | viral, generator, builder | viral, generator, builder, building, shareable, go, name, generators, quiz, makers, avatar, creators |
| `webapp-testing` | Toolkit for interacting with and testing local web applications using Playwright. Supports verifying frontend functionality, debugging UI behavior, capturing... | webapp | webapp, testing, toolkit, interacting, local, web, applications, playwright, supports, verifying, frontend, functionality |
## general (128)
## general (130)
| Skill | Description | Tags | Triggers |
| --- | --- | --- | --- |
| `address-github-comments` | Use when you need to address review or issue comments on an open GitHub Pull Request using the gh CLI. | address, github, comments | address, github, comments, review, issue, open, pull, request, gh, cli |
| `agent-manager-skill` | Manage multiple local CLI agents via tmux sessions (start/stop/monitor/assign) with cron-friendly scheduling. | agent, manager, skill | agent, manager, skill, multiple, local, cli, agents, via, tmux, sessions, start, stop |
| `algorithmic-art` | Creating algorithmic art using p5.js with seeded randomness and interactive parameter exploration. Use this when users request creating art using code, gener... | algorithmic, art | algorithmic, art, creating, p5, js, seeded, randomness, interactive, parameter, exploration, users, request |
| `angular-best-practices` | Angular performance optimization and best practices guide. Use when writing, reviewing, or refactoring Angular code for optimal performance, bundle size, and... | angular, best, practices | angular, best, practices, performance, optimization, writing, reviewing, refactoring, code, optimal, bundle, size |
| `angular-migration` | Migrate from AngularJS to Angular using hybrid mode, incremental component rewriting, and dependency injection updates. Use when upgrading AngularJS applicat... | angular, migration | angular, migration, migrate, angularjs, hybrid, mode, incremental, component, rewriting, dependency, injection, updates |
| `anti-reversing-techniques` | Understand anti-reversing, obfuscation, and protection techniques encountered during software analysis. Use when analyzing protected binaries, bypassing anti... | anti, reversing, techniques | anti, reversing, techniques, understand, obfuscation, protection, encountered, during, software, analysis, analyzing, protected |
| `app-builder` | Main application building orchestrator. Creates full-stack applications from natural language requests. Determines project type, selects tech stack, coordina... | app, builder | app, builder, main, application, building, orchestrator, creates, full, stack, applications, natural, language |
@@ -334,6 +338,7 @@ TRIGGER: "shopify", "shopify app", "checkout extension",... | shopify | shopify,
| `commit` | Create commit messages following Sentry conventions. Use when committing code changes, writing commit messages, or formatting git history. Follows convention... | commit | commit, messages, following, sentry, conventions, committing, code, changes, writing, formatting, git, history |
| `comprehensive-review-full-review` | Use when working with comprehensive review full review | comprehensive, full | comprehensive, full, review, working |
| `comprehensive-review-pr-enhance` | You are a PR optimization expert specializing in creating high-quality pull requests that facilitate efficient code reviews. Generate comprehensive PR descri... | comprehensive, pr, enhance | comprehensive, pr, enhance, review, optimization, specializing, creating, high, quality, pull, requests, facilitate |
| `computer-vision-expert` | SOTA Computer Vision Expert (2026). Specialized in YOLO26, Segment Anything 3 (SAM 3), Vision Language Models, and real-time spatial analysis. | computer, vision | computer, vision, sota, 2026, specialized, yolo26, segment, anything, sam, language, models, real |
| `concise-planning` | Use when a user asks for a plan for a coding task, to generate a clear, actionable, and atomic checklist. | concise, planning | concise, planning, user, asks, plan, coding, task, generate, clear, actionable, atomic, checklist |
| `context-compression` | Design and evaluate compression strategies for long-running sessions | compression | compression, context, evaluate, long, running, sessions |
| `context-fundamentals` | Understand what context is, why it matters, and the anatomy of context in agent systems | fundamentals | fundamentals, context, understand, what, why, matters, anatomy, agent |

View File

@@ -7,6 +7,30 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
---
## [4.8.0] - 2026-02-04 - "Computer Vision & Angular"
> Major SOTA Computer Vision update and complete Angular 20+ skill suite.
### Added
- **New Skill**: `computer-vision-expert` Updated foundational skill for 2026 SOTA Computer Vision (YOLO26, SAM 3, VLMs).
- **New Suite**: `angular` 4 comprehensive skills for Angular v20+ development:
- `angular` Core patterns (Signals, Standalone, Zoneless).
- `angular-best-practices` Performance and optimization.
- `angular-state-management` Signal-based state management (NgRx, ComponentStore).
- `angular-ui-patterns` UI/UX engineering patterns.
### Registry
- **Total Skills**: 631 (from 626). Catalog regenerated.
### Credits
- [@PabloSMD](https://github.com/PabloSMD) computer-vision-expert (PR #58)
- [@chauey](https://github.com/chauey) angular suite (PR #60)
---
## [4.7.0] - 2026-02-03 - "Installer Fix & OpenCode Docs"
> Critical installer fix for Windows and OpenCode documentation completion.

View File

@@ -1,6 +1,6 @@
# 🌌 Antigravity Awesome Skills: 626+ Agentic Skills for Claude Code, Gemini CLI, Cursor, Copilot & More
# 🌌 Antigravity Awesome Skills: 631+ Agentic Skills for Claude Code, Gemini CLI, Cursor, Copilot & More
> **The Ultimate Collection of 626+ Universal Agentic Skills for AI Coding Assistants — Claude Code, Gemini CLI, Codex CLI, Antigravity IDE, GitHub Copilot, Cursor, OpenCode, AdaL**
> **The Ultimate Collection of 631+ Universal Agentic Skills for AI Coding Assistants — Claude Code, Gemini CLI, Codex CLI, Antigravity IDE, GitHub Copilot, Cursor, OpenCode, AdaL**
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Claude Code](https://img.shields.io/badge/Claude%20Code-Anthropic-purple)](https://claude.ai)
@@ -10,9 +10,10 @@
[![Copilot](https://img.shields.io/badge/GitHub%20Copilot-VSCode-lightblue)](https://github.com/features/copilot)
[![OpenCode](https://img.shields.io/badge/OpenCode-CLI-gray)](https://github.com/opencode-ai/opencode)
[![Antigravity](https://img.shields.io/badge/Antigravity-DeepMind-red)](https://github.com/sickn33/antigravity-awesome-skills)
[![AdaL](https://img.shields.io/badge/AdaL-Self--evolving%20Agent-pink)](https://github.com/HumanSignal/Adala)
[![AdaL CLI](https://img.shields.io/badge/AdaL%20CLI-SylphAI-pink)](https://sylph.ai/)
[![ASK Supported](https://img.shields.io/badge/ASK-Supported-blue)](https://github.com/yeasy/ask)
**Antigravity Awesome Skills** is a curated, battle-tested library of **626 high-performance agentic skills** designed to work seamlessly across all major AI coding assistants:
**Antigravity Awesome Skills** is a curated, battle-tested library of **631 high-performance agentic skills** designed to work seamlessly across all major AI coding assistants:
- 🟣 **Claude Code** (Anthropic CLI)
- 🔵 **Gemini CLI** (Google DeepMind)
@@ -21,7 +22,7 @@
- 🩵 **GitHub Copilot** (VSCode Extension)
- 🟠 **Cursor** (AI-native IDE)
-**OpenCode** (Open-source CLI)
- 🌸 **AdaL** (Self-evolving AI Agent)
- 🌸 **AdaL CLI** (Self-evolving Coding Agent)
This repository provides essential skills to transform your AI assistant into a **full-stack digital agency**, including official capabilities from **Anthropic**, **OpenAI**, **Google**, **Supabase**, and **Vercel Labs**.
@@ -31,7 +32,7 @@ This repository provides essential skills to transform your AI assistant into a
- [🔌 Compatibility & Invocation](#compatibility--invocation)
- [📦 Features & Categories](#features--categories)
- [🎁 Curated Collections (Bundles)](#curated-collections)
- [📚 Browse 626+ Skills](#browse-626-skills)
- [📚 Browse 631+ Skills](#browse-631-skills)
- [🛠️ Installation](#installation)
- [🤝 How to Contribute](#how-to-contribute)
- [👥 Contributors & Credits](#credits--sources)
@@ -86,16 +87,16 @@ Once installed, just ask your agent naturally:
These skills follow the universal **SKILL.md** format and work with any AI coding assistant that supports agentic skills.
| Tool | Type | Invocation Example | Path |
| :-------------- | :---- | :-------------------------------- | :---------------- |
| **Claude Code** | CLI | `>> /skill-name help me...` | `.claude/skills/` |
| **Gemini CLI** | CLI | `(User Prompt) Use skill-name...` | `.gemini/skills/` |
| **Codex CLI** | CLI | `(User Prompt) Use skill-name...` | `.codex/skills/` |
| **Antigravity** | IDE | `(Agent Mode) Use skill...` | `.agent/skills/` |
| **Cursor** | IDE | `@skill-name (in Chat)` | `.cursor/skills/` |
| **Copilot** | Ext | `(Paste content manually)` | N/A |
| **OpenCode** | CLI | `opencode run @skill-name` | `.agent/skills/` |
| **AdaL** | Agent | `(Agent Mode) Use skill...` | `.agent/skills/` |
| Tool | Type | Invocation Example | Path |
| :-------------- | :--- | :-------------------------------- | :---------------- |
| **Claude Code** | CLI | `>> /skill-name help me...` | `.claude/skills/` |
| **Gemini CLI** | CLI | `(User Prompt) Use skill-name...` | `.gemini/skills/` |
| **Codex CLI** | CLI | `(User Prompt) Use skill-name...` | `.codex/skills/` |
| **Antigravity** | IDE | `(Agent Mode) Use skill...` | `.agent/skills/` |
| **Cursor** | IDE | `@skill-name (in Chat)` | `.cursor/skills/` |
| **Copilot** | Ext | `(Paste content manually)` | N/A |
| **OpenCode** | CLI | `opencode run @skill-name` | `.agent/skills/` |
| **AdaL CLI** | CLI | `(Auto) Skills load on-demand` | `.adal/skills/` |
> [!TIP]
> **Universal Path**: We recommend cloning to `.agent/skills/`. Most modern tools (Antigravity, recent CLIs) look here by default.
@@ -131,7 +132,7 @@ The repository is organized into specialized domains to transform your AI into a
[Check out our Starter Packs in docs/BUNDLES.md](docs/BUNDLES.md) to find the perfect toolkit for your role.
## Browse 626+ Skills
## Browse 631+ Skills
We have moved the full skill registry to a dedicated catalog to keep this README clean.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 50 KiB

View File

@@ -1,5 +1,5 @@
{
"generatedAt": "2026-02-03T09:20:12.539Z",
"generatedAt": "2026-02-04T07:52:07.897Z",
"aliases": {
"accessibility-compliance-audit": "accessibility-compliance-accessibility-audit",
"active directory attacks": "active-directory-attacks",

View File

@@ -1,5 +1,5 @@
{
"generatedAt": "2026-02-03T09:20:12.539Z",
"generatedAt": "2026-02-04T07:52:07.897Z",
"bundles": {
"core-dev": {
"description": "Core development skills across languages, frameworks, and backend/frontend fundamentals.",
@@ -259,6 +259,7 @@
"skills": [
"airflow-dag-patterns",
"analytics-tracking",
"angular-ui-patterns",
"blockrun",
"business-analyst",
"cc-skill-backend-patterns",

View File

@@ -1,6 +1,6 @@
{
"generatedAt": "2026-02-03T09:20:12.539Z",
"total": 626,
"generatedAt": "2026-02-04T07:52:07.897Z",
"total": 631,
"skills": [
{
"id": "3d-web-experience",
@@ -505,6 +505,56 @@
],
"path": "skills/analytics-tracking/SKILL.md"
},
{
"id": "angular",
"name": "angular",
"description": "Modern Angular (v20+) expert with deep knowledge of Signals, Standalone Components, Zoneless applications, SSR/Hydration, and reactive patterns. Use PROACTIVELY for Angular development, component architecture, state management, performance optimization, and migration to modern patterns.",
"category": "architecture",
"tags": [
"angular"
],
"triggers": [
"angular",
"v20",
"deep",
"knowledge",
"signals",
"standalone",
"components",
"zoneless",
"applications",
"ssr",
"hydration",
"reactive"
],
"path": "skills/angular/SKILL.md"
},
{
"id": "angular-best-practices",
"name": "angular-best-practices",
"description": "Angular performance optimization and best practices guide. Use when writing, reviewing, or refactoring Angular code for optimal performance, bundle size, and rendering efficiency.",
"category": "general",
"tags": [
"angular",
"best",
"practices"
],
"triggers": [
"angular",
"best",
"practices",
"performance",
"optimization",
"writing",
"reviewing",
"refactoring",
"code",
"optimal",
"bundle",
"size"
],
"path": "skills/angular-best-practices/SKILL.md"
},
{
"id": "angular-migration",
"name": "angular-migration",
@@ -530,6 +580,56 @@
],
"path": "skills/angular-migration/SKILL.md"
},
{
"id": "angular-state-management",
"name": "angular-state-management",
"description": "Master modern Angular state management with Signals, NgRx, and RxJS. Use when setting up global state, managing component stores, choosing between state solutions, or migrating from legacy patterns.",
"category": "architecture",
"tags": [
"angular",
"state"
],
"triggers": [
"angular",
"state",
"signals",
"ngrx",
"rxjs",
"setting",
"up",
"global",
"managing",
"component",
"stores",
"choosing"
],
"path": "skills/angular-state-management/SKILL.md"
},
{
"id": "angular-ui-patterns",
"name": "angular-ui-patterns",
"description": "Modern Angular UI patterns for loading states, error handling, and data display. Use when building UI components, handling async data, or managing component states.",
"category": "data-ai",
"tags": [
"angular",
"ui"
],
"triggers": [
"angular",
"ui",
"loading",
"states",
"error",
"handling",
"data",
"display",
"building",
"components",
"async",
"managing"
],
"path": "skills/angular-ui-patterns/SKILL.md"
},
{
"id": "anti-reversing-techniques",
"name": "anti-reversing-techniques",
@@ -3091,6 +3191,31 @@
],
"path": "skills/computer-use-agents/SKILL.md"
},
{
"id": "computer-vision-expert",
"name": "computer-vision-expert",
"description": "SOTA Computer Vision Expert (2026). Specialized in YOLO26, Segment Anything 3 (SAM 3), Vision Language Models, and real-time spatial analysis.",
"category": "general",
"tags": [
"computer",
"vision"
],
"triggers": [
"computer",
"vision",
"sota",
"2026",
"specialized",
"yolo26",
"segment",
"anything",
"sam",
"language",
"models",
"real"
],
"path": "skills/computer-vision-expert/SKILL.md"
},
{
"id": "concise-planning",
"name": "concise-planning",

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "antigravity-awesome-skills",
"version": "4.2.0",
"version": "4.8.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "antigravity-awesome-skills",
"version": "4.2.0",
"version": "4.8.0",
"license": "MIT",
"dependencies": {
"yaml": "^2.8.2"

View File

@@ -1,6 +1,6 @@
{
"name": "antigravity-awesome-skills",
"version": "4.7.0",
"version": "4.8.0",
"description": "626+ agentic skills for Claude Code, Gemini CLI, Cursor, Antigravity & more. Installer CLI.",
"license": "MIT",
"scripts": {

View File

@@ -0,0 +1,559 @@
---
name: angular-best-practices
description: Angular performance optimization and best practices guide. Use when writing, reviewing, or refactoring Angular code for optimal performance, bundle size, and rendering efficiency.
risk: safe
source: self
---
# Angular Best Practices
Comprehensive performance optimization guide for Angular applications. Contains prioritized rules for eliminating performance bottlenecks, optimizing bundles, and improving rendering.
## When to Apply
Reference these guidelines when:
- Writing new Angular components or pages
- Implementing data fetching patterns
- Reviewing code for performance issues
- Refactoring existing Angular code
- Optimizing bundle size or load times
- Configuring SSR/hydration
---
## Rule Categories by Priority
| Priority | Category | Impact | Focus |
| -------- | --------------------- | ---------- | ------------------------------- |
| 1 | Change Detection | CRITICAL | Signals, OnPush, Zoneless |
| 2 | Async Waterfalls | CRITICAL | RxJS patterns, SSR preloading |
| 3 | Bundle Optimization | CRITICAL | Lazy loading, tree shaking |
| 4 | Rendering Performance | HIGH | @defer, trackBy, virtualization |
| 5 | Server-Side Rendering | HIGH | Hydration, prerendering |
| 6 | Template Optimization | MEDIUM | Control flow, pipes |
| 7 | State Management | MEDIUM | Signal patterns, selectors |
| 8 | Memory Management | LOW-MEDIUM | Cleanup, subscriptions |
---
## 1. Change Detection (CRITICAL)
### Use OnPush Change Detection
```typescript
// CORRECT - OnPush with Signals
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
template: `<div>{{ count() }}</div>`,
})
export class CounterComponent {
count = signal(0);
}
// WRONG - Default change detection
@Component({
template: `<div>{{ count }}</div>`, // Checked every cycle
})
export class CounterComponent {
count = 0;
}
```
### Prefer Signals Over Mutable Properties
```typescript
// CORRECT - Signals trigger precise updates
@Component({
template: `
<h1>{{ title() }}</h1>
<p>Count: {{ count() }}</p>
`,
})
export class DashboardComponent {
title = signal("Dashboard");
count = signal(0);
}
// WRONG - Mutable properties require zone.js checks
@Component({
template: `
<h1>{{ title }}</h1>
<p>Count: {{ count }}</p>
`,
})
export class DashboardComponent {
title = "Dashboard";
count = 0;
}
```
### Enable Zoneless for New Projects
```typescript
// main.ts - Zoneless Angular (v20+)
bootstrapApplication(AppComponent, {
providers: [provideZonelessChangeDetection()],
});
```
**Benefits:**
- No zone.js patches on async APIs
- Smaller bundle (~15KB savings)
- Clean stack traces for debugging
- Better micro-frontend compatibility
---
## 2. Async Operations & Waterfalls (CRITICAL)
### Eliminate Sequential Data Fetching
```typescript
// WRONG - Nested subscriptions create waterfalls
this.route.params.subscribe((params) => {
// 1. Wait for params
this.userService.getUser(params.id).subscribe((user) => {
// 2. Wait for user
this.postsService.getPosts(user.id).subscribe((posts) => {
// 3. Wait for posts
});
});
});
// CORRECT - Parallel execution with forkJoin
forkJoin({
user: this.userService.getUser(id),
posts: this.postsService.getPosts(id),
}).subscribe((data) => {
// Fetched in parallel
});
// CORRECT - Flatten dependent calls with switchMap
this.route.params
.pipe(
map((p) => p.id),
switchMap((id) => this.userService.getUser(id)),
)
.subscribe();
```
### Avoid Client-Side Waterfalls in SSR
```typescript
// CORRECT - Use resolvers or blocking hydration for critical data
export const route: Route = {
path: "profile/:id",
resolve: { data: profileResolver }, // Fetched on server before navigation
component: ProfileComponent,
};
// WRONG - Component fetches data on init
class ProfileComponent implements OnInit {
ngOnInit() {
// Starts ONLY after JS loads and component renders
this.http.get("/api/profile").subscribe();
}
}
```
---
## 3. Bundle Optimization (CRITICAL)
### Lazy Load Routes
```typescript
// CORRECT - Lazy load feature routes
export const routes: Routes = [
{
path: "admin",
loadChildren: () =>
import("./admin/admin.routes").then((m) => m.ADMIN_ROUTES),
},
{
path: "dashboard",
loadComponent: () =>
import("./dashboard/dashboard.component").then(
(m) => m.DashboardComponent,
),
},
];
// WRONG - Eager loading everything
import { AdminModule } from "./admin/admin.module";
export const routes: Routes = [
{ path: "admin", component: AdminComponent }, // In main bundle
];
```
### Use @defer for Heavy Components
```html
<!-- CORRECT - Heavy component loads on demand -->
@defer (on viewport) {
<app-analytics-chart [data]="data()" />
} @placeholder {
<div class="chart-skeleton"></div>
}
<!-- WRONG - Heavy component in initial bundle -->
<app-analytics-chart [data]="data()" />
```
### Avoid Barrel File Re-exports
```typescript
// WRONG - Imports entire barrel, breaks tree-shaking
import { Button, Modal, Table } from "@shared/components";
// CORRECT - Direct imports
import { Button } from "@shared/components/button/button.component";
import { Modal } from "@shared/components/modal/modal.component";
```
### Dynamic Import Third-Party Libraries
```typescript
// CORRECT - Load heavy library on demand
async loadChart() {
const { Chart } = await import('chart.js');
this.chart = new Chart(this.canvas, config);
}
// WRONG - Bundle Chart.js in main chunk
import { Chart } from 'chart.js';
```
---
## 4. Rendering Performance (HIGH)
### Always Use trackBy with @for
```html
<!-- CORRECT - Efficient DOM updates -->
@for (item of items(); track item.id) {
<app-item-card [item]="item" />
}
<!-- WRONG - Entire list re-renders on any change -->
@for (item of items(); track $index) {
<app-item-card [item]="item" />
}
```
### Use Virtual Scrolling for Large Lists
```typescript
import { CdkVirtualScrollViewport, CdkFixedSizeVirtualScroll } from '@angular/cdk/scrolling';
@Component({
imports: [CdkVirtualScrollViewport, CdkFixedSizeVirtualScroll],
template: `
<cdk-virtual-scroll-viewport itemSize="50" class="viewport">
<div *cdkVirtualFor="let item of items" class="item">
{{ item.name }}
</div>
</cdk-virtual-scroll-viewport>
`
})
```
### Prefer Pure Pipes Over Methods
```typescript
// CORRECT - Pure pipe, memoized
@Pipe({ name: 'filterActive', standalone: true, pure: true })
export class FilterActivePipe implements PipeTransform {
transform(items: Item[]): Item[] {
return items.filter(i => i.active);
}
}
// Template
@for (item of items() | filterActive; track item.id) { ... }
// WRONG - Method called every change detection
@for (item of getActiveItems(); track item.id) { ... }
```
### Use computed() for Derived Data
```typescript
// CORRECT - Computed, cached until dependencies change
export class ProductStore {
products = signal<Product[]>([]);
filter = signal('');
filteredProducts = computed(() => {
const f = this.filter().toLowerCase();
return this.products().filter(p =>
p.name.toLowerCase().includes(f)
);
});
}
// WRONG - Recalculates every access
get filteredProducts() {
return this.products.filter(p =>
p.name.toLowerCase().includes(this.filter)
);
}
```
---
## 5. Server-Side Rendering (HIGH)
### Configure Incremental Hydration
```typescript
// app.config.ts
import {
provideClientHydration,
withIncrementalHydration,
} from "@angular/platform-browser";
export const appConfig: ApplicationConfig = {
providers: [
provideClientHydration(withIncrementalHydration(), withEventReplay()),
],
};
```
### Defer Non-Critical Content
```html
<!-- Critical above-the-fold content -->
<app-header />
<app-hero />
<!-- Below-fold deferred with hydration triggers -->
@defer (hydrate on viewport) {
<app-product-grid />
} @defer (hydrate on interaction) {
<app-chat-widget />
}
```
### Use TransferState for SSR Data
```typescript
@Injectable({ providedIn: "root" })
export class DataService {
private http = inject(HttpClient);
private transferState = inject(TransferState);
private platformId = inject(PLATFORM_ID);
getData(key: string): Observable<Data> {
const stateKey = makeStateKey<Data>(key);
if (isPlatformBrowser(this.platformId)) {
const cached = this.transferState.get(stateKey, null);
if (cached) {
this.transferState.remove(stateKey);
return of(cached);
}
}
return this.http.get<Data>(`/api/${key}`).pipe(
tap((data) => {
if (isPlatformServer(this.platformId)) {
this.transferState.set(stateKey, data);
}
}),
);
}
}
```
---
## 6. Template Optimization (MEDIUM)
### Use New Control Flow Syntax
```html
<!-- CORRECT - New control flow (faster, smaller bundle) -->
@if (user()) {
<span>{{ user()!.name }}</span>
} @else {
<span>Guest</span>
} @for (item of items(); track item.id) {
<app-item [item]="item" />
} @empty {
<p>No items</p>
}
<!-- WRONG - Legacy structural directives -->
<span *ngIf="user; else guest">{{ user.name }}</span>
<ng-template #guest><span>Guest</span></ng-template>
```
### Avoid Complex Template Expressions
```typescript
// CORRECT - Precompute in component
class Component {
items = signal<Item[]>([]);
sortedItems = computed(() =>
[...this.items()].sort((a, b) => a.name.localeCompare(b.name))
);
}
// Template
@for (item of sortedItems(); track item.id) { ... }
// WRONG - Sorting in template every render
@for (item of items() | sort:'name'; track item.id) { ... }
```
---
## 7. State Management (MEDIUM)
### Use Selectors to Prevent Re-renders
```typescript
// CORRECT - Selective subscription
@Component({
template: `<span>{{ userName() }}</span>`,
})
class HeaderComponent {
private store = inject(Store);
// Only re-renders when userName changes
userName = this.store.selectSignal(selectUserName);
}
// WRONG - Subscribing to entire state
@Component({
template: `<span>{{ state().user.name }}</span>`,
})
class HeaderComponent {
private store = inject(Store);
// Re-renders on ANY state change
state = toSignal(this.store);
}
```
### Colocate State with Features
```typescript
// CORRECT - Feature-scoped store
@Injectable() // NOT providedIn: 'root'
export class ProductStore { ... }
@Component({
providers: [ProductStore], // Scoped to component tree
})
export class ProductPageComponent {
store = inject(ProductStore);
}
// WRONG - Everything in global store
@Injectable({ providedIn: 'root' })
export class GlobalStore {
// Contains ALL app state - hard to tree-shake
}
```
---
## 8. Memory Management (LOW-MEDIUM)
### Use takeUntilDestroyed for Subscriptions
```typescript
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
@Component({...})
export class DataComponent {
private destroyRef = inject(DestroyRef);
constructor() {
this.data$.pipe(
takeUntilDestroyed(this.destroyRef)
).subscribe(data => this.process(data));
}
}
// WRONG - Manual subscription management
export class DataComponent implements OnDestroy {
private subscription!: Subscription;
ngOnInit() {
this.subscription = this.data$.subscribe(...);
}
ngOnDestroy() {
this.subscription.unsubscribe(); // Easy to forget
}
}
```
### Prefer Signals Over Subscriptions
```typescript
// CORRECT - No subscription needed
@Component({
template: `<div>{{ data().name }}</div>`,
})
export class Component {
data = toSignal(this.service.data$, { initialValue: null });
}
// WRONG - Manual subscription
@Component({
template: `<div>{{ data?.name }}</div>`,
})
export class Component implements OnInit, OnDestroy {
data: Data | null = null;
private sub!: Subscription;
ngOnInit() {
this.sub = this.service.data$.subscribe((d) => (this.data = d));
}
ngOnDestroy() {
this.sub.unsubscribe();
}
}
```
---
## Quick Reference Checklist
### New Component
- [ ] `changeDetection: ChangeDetectionStrategy.OnPush`
- [ ] `standalone: true`
- [ ] Signals for state (`signal()`, `input()`, `output()`)
- [ ] `inject()` for dependencies
- [ ] `@for` with `track` expression
### Performance Review
- [ ] No methods in templates (use pipes or computed)
- [ ] Large lists virtualized
- [ ] Heavy components deferred
- [ ] Routes lazy-loaded
- [ ] Third-party libs dynamically imported
### SSR Check
- [ ] Hydration configured
- [ ] Critical content renders first
- [ ] Non-critical content uses `@defer (hydrate on ...)`
- [ ] TransferState for server-fetched data
---
## Resources
- [Angular Performance Guide](https://angular.dev/best-practices/performance)
- [Zoneless Angular](https://angular.dev/guide/experimental/zoneless)
- [Angular SSR Guide](https://angular.dev/guide/ssr)
- [Change Detection Deep Dive](https://angular.dev/guide/change-detection)

View File

@@ -0,0 +1,634 @@
---
name: angular-state-management
description: Master modern Angular state management with Signals, NgRx, and RxJS. Use when setting up global state, managing component stores, choosing between state solutions, or migrating from legacy patterns.
risk: safe
source: self
---
# Angular State Management
Comprehensive guide to modern Angular state management patterns, from Signal-based local state to global stores and server state synchronization.
## When to Use This Skill
- Setting up global state management in Angular
- Choosing between Signals, NgRx, or Akita
- Managing component-level stores
- Implementing optimistic updates
- Debugging state-related issues
- Migrating from legacy state patterns
## Do Not Use This Skill When
- The task is unrelated to Angular state management
- You need React state management → use `react-state-management`
---
## Core Concepts
### State Categories
| Type | Description | Solutions |
| ---------------- | ---------------------------- | --------------------- |
| **Local State** | Component-specific, UI state | Signals, `signal()` |
| **Shared State** | Between related components | Signal services |
| **Global State** | App-wide, complex | NgRx, Akita, Elf |
| **Server State** | Remote data, caching | NgRx Query, RxAngular |
| **URL State** | Route parameters | ActivatedRoute |
| **Form State** | Input values, validation | Reactive Forms |
### Selection Criteria
```
Small app, simple state → Signal Services
Medium app, moderate state → Component Stores
Large app, complex state → NgRx Store
Heavy server interaction → NgRx Query + Signal Services
Real-time updates → RxAngular + Signals
```
---
## Quick Start: Signal-Based State
### Pattern 1: Simple Signal Service
```typescript
// services/counter.service.ts
import { Injectable, signal, computed } from "@angular/core";
@Injectable({ providedIn: "root" })
export class CounterService {
// Private writable signals
private _count = signal(0);
// Public read-only
readonly count = this._count.asReadonly();
readonly doubled = computed(() => this._count() * 2);
readonly isPositive = computed(() => this._count() > 0);
increment() {
this._count.update((v) => v + 1);
}
decrement() {
this._count.update((v) => v - 1);
}
reset() {
this._count.set(0);
}
}
// Usage in component
@Component({
template: `
<p>Count: {{ counter.count() }}</p>
<p>Doubled: {{ counter.doubled() }}</p>
<button (click)="counter.increment()">+</button>
`,
})
export class CounterComponent {
counter = inject(CounterService);
}
```
### Pattern 2: Feature Signal Store
```typescript
// stores/user.store.ts
import { Injectable, signal, computed, inject } from "@angular/core";
import { HttpClient } from "@angular/common/http";
import { toSignal } from "@angular/core/rxjs-interop";
interface User {
id: string;
name: string;
email: string;
}
interface UserState {
user: User | null;
loading: boolean;
error: string | null;
}
@Injectable({ providedIn: "root" })
export class UserStore {
private http = inject(HttpClient);
// State signals
private _user = signal<User | null>(null);
private _loading = signal(false);
private _error = signal<string | null>(null);
// Selectors (read-only computed)
readonly user = computed(() => this._user());
readonly loading = computed(() => this._loading());
readonly error = computed(() => this._error());
readonly isAuthenticated = computed(() => this._user() !== null);
readonly displayName = computed(() => this._user()?.name ?? "Guest");
// Actions
async loadUser(id: string) {
this._loading.set(true);
this._error.set(null);
try {
const user = await fetch(`/api/users/${id}`).then((r) => r.json());
this._user.set(user);
} catch (e) {
this._error.set("Failed to load user");
} finally {
this._loading.set(false);
}
}
updateUser(updates: Partial<User>) {
this._user.update((user) => (user ? { ...user, ...updates } : null));
}
logout() {
this._user.set(null);
this._error.set(null);
}
}
```
### Pattern 3: SignalStore (NgRx Signals)
```typescript
// stores/products.store.ts
import {
signalStore,
withState,
withMethods,
withComputed,
patchState,
} from "@ngrx/signals";
import { inject } from "@angular/core";
import { ProductService } from "./product.service";
interface ProductState {
products: Product[];
loading: boolean;
filter: string;
}
const initialState: ProductState = {
products: [],
loading: false,
filter: "",
};
export const ProductStore = signalStore(
{ providedIn: "root" },
withState(initialState),
withComputed((store) => ({
filteredProducts: computed(() => {
const filter = store.filter().toLowerCase();
return store
.products()
.filter((p) => p.name.toLowerCase().includes(filter));
}),
totalCount: computed(() => store.products().length),
})),
withMethods((store, productService = inject(ProductService)) => ({
async loadProducts() {
patchState(store, { loading: true });
try {
const products = await productService.getAll();
patchState(store, { products, loading: false });
} catch {
patchState(store, { loading: false });
}
},
setFilter(filter: string) {
patchState(store, { filter });
},
addProduct(product: Product) {
patchState(store, ({ products }) => ({
products: [...products, product],
}));
},
})),
);
// Usage
@Component({
template: `
<input (input)="store.setFilter($event.target.value)" />
@if (store.loading()) {
<app-spinner />
} @else {
@for (product of store.filteredProducts(); track product.id) {
<app-product-card [product]="product" />
}
}
`,
})
export class ProductListComponent {
store = inject(ProductStore);
ngOnInit() {
this.store.loadProducts();
}
}
```
---
## NgRx Store (Global State)
### Setup
```typescript
// store/app.state.ts
import { ActionReducerMap } from "@ngrx/store";
export interface AppState {
user: UserState;
cart: CartState;
}
export const reducers: ActionReducerMap<AppState> = {
user: userReducer,
cart: cartReducer,
};
// main.ts
bootstrapApplication(AppComponent, {
providers: [
provideStore(reducers),
provideEffects([UserEffects, CartEffects]),
provideStoreDevtools({ maxAge: 25 }),
],
});
```
### Feature Slice Pattern
```typescript
// store/user/user.actions.ts
import { createActionGroup, props, emptyProps } from "@ngrx/store";
export const UserActions = createActionGroup({
source: "User",
events: {
"Load User": props<{ userId: string }>(),
"Load User Success": props<{ user: User }>(),
"Load User Failure": props<{ error: string }>(),
"Update User": props<{ updates: Partial<User> }>(),
Logout: emptyProps(),
},
});
```
```typescript
// store/user/user.reducer.ts
import { createReducer, on } from "@ngrx/store";
import { UserActions } from "./user.actions";
export interface UserState {
user: User | null;
loading: boolean;
error: string | null;
}
const initialState: UserState = {
user: null,
loading: false,
error: null,
};
export const userReducer = createReducer(
initialState,
on(UserActions.loadUser, (state) => ({
...state,
loading: true,
error: null,
})),
on(UserActions.loadUserSuccess, (state, { user }) => ({
...state,
user,
loading: false,
})),
on(UserActions.loadUserFailure, (state, { error }) => ({
...state,
loading: false,
error,
})),
on(UserActions.logout, () => initialState),
);
```
```typescript
// store/user/user.selectors.ts
import { createFeatureSelector, createSelector } from "@ngrx/store";
import { UserState } from "./user.reducer";
export const selectUserState = createFeatureSelector<UserState>("user");
export const selectUser = createSelector(
selectUserState,
(state) => state.user,
);
export const selectUserLoading = createSelector(
selectUserState,
(state) => state.loading,
);
export const selectIsAuthenticated = createSelector(
selectUser,
(user) => user !== null,
);
```
```typescript
// store/user/user.effects.ts
import { Injectable, inject } from "@angular/core";
import { Actions, createEffect, ofType } from "@ngrx/effects";
import { switchMap, map, catchError, of } from "rxjs";
@Injectable()
export class UserEffects {
private actions$ = inject(Actions);
private userService = inject(UserService);
loadUser$ = createEffect(() =>
this.actions$.pipe(
ofType(UserActions.loadUser),
switchMap(({ userId }) =>
this.userService.getUser(userId).pipe(
map((user) => UserActions.loadUserSuccess({ user })),
catchError((error) =>
of(UserActions.loadUserFailure({ error: error.message })),
),
),
),
),
);
}
```
### Component Usage
```typescript
@Component({
template: `
@if (loading()) {
<app-spinner />
} @else if (user(); as user) {
<h1>Welcome, {{ user.name }}</h1>
<button (click)="logout()">Logout</button>
}
`,
})
export class HeaderComponent {
private store = inject(Store);
user = this.store.selectSignal(selectUser);
loading = this.store.selectSignal(selectUserLoading);
logout() {
this.store.dispatch(UserActions.logout());
}
}
```
---
## RxJS-Based Patterns
### Component Store (Local Feature State)
```typescript
// stores/todo.store.ts
import { Injectable } from "@angular/core";
import { ComponentStore } from "@ngrx/component-store";
import { switchMap, tap, catchError, EMPTY } from "rxjs";
interface TodoState {
todos: Todo[];
loading: boolean;
}
@Injectable()
export class TodoStore extends ComponentStore<TodoState> {
constructor(private todoService: TodoService) {
super({ todos: [], loading: false });
}
// Selectors
readonly todos$ = this.select((state) => state.todos);
readonly loading$ = this.select((state) => state.loading);
readonly completedCount$ = this.select(
this.todos$,
(todos) => todos.filter((t) => t.completed).length,
);
// Updaters
readonly addTodo = this.updater((state, todo: Todo) => ({
...state,
todos: [...state.todos, todo],
}));
readonly toggleTodo = this.updater((state, id: string) => ({
...state,
todos: state.todos.map((t) =>
t.id === id ? { ...t, completed: !t.completed } : t,
),
}));
// Effects
readonly loadTodos = this.effect<void>((trigger$) =>
trigger$.pipe(
tap(() => this.patchState({ loading: true })),
switchMap(() =>
this.todoService.getAll().pipe(
tap({
next: (todos) => this.patchState({ todos, loading: false }),
error: () => this.patchState({ loading: false }),
}),
catchError(() => EMPTY),
),
),
),
);
}
```
---
## Server State with Signals
### HTTP + Signals Pattern
```typescript
// services/api.service.ts
import { Injectable, signal, inject } from "@angular/core";
import { HttpClient } from "@angular/common/http";
import { toSignal } from "@angular/core/rxjs-interop";
interface ApiState<T> {
data: T | null;
loading: boolean;
error: string | null;
}
@Injectable({ providedIn: "root" })
export class ProductApiService {
private http = inject(HttpClient);
private _state = signal<ApiState<Product[]>>({
data: null,
loading: false,
error: null,
});
readonly products = computed(() => this._state().data ?? []);
readonly loading = computed(() => this._state().loading);
readonly error = computed(() => this._state().error);
async fetchProducts(): Promise<void> {
this._state.update((s) => ({ ...s, loading: true, error: null }));
try {
const data = await firstValueFrom(
this.http.get<Product[]>("/api/products"),
);
this._state.update((s) => ({ ...s, data, loading: false }));
} catch (e) {
this._state.update((s) => ({
...s,
loading: false,
error: "Failed to fetch products",
}));
}
}
// Optimistic update
async deleteProduct(id: string): Promise<void> {
const previousData = this._state().data;
// Optimistically remove
this._state.update((s) => ({
...s,
data: s.data?.filter((p) => p.id !== id) ?? null,
}));
try {
await firstValueFrom(this.http.delete(`/api/products/${id}`));
} catch {
// Rollback on error
this._state.update((s) => ({ ...s, data: previousData }));
}
}
}
```
---
## Best Practices
### Do's
| Practice | Why |
| ---------------------------------- | ---------------------------------- |
| Use Signals for local state | Simple, reactive, no subscriptions |
| Use `computed()` for derived data | Auto-updates, memoized |
| Colocate state with feature | Easier to maintain |
| Use NgRx for complex flows | Actions, effects, devtools |
| Prefer `inject()` over constructor | Cleaner, works in factories |
### Don'ts
| Anti-Pattern | Instead |
| --------------------------------- | ----------------------------------------------------- |
| Store derived data | Use `computed()` |
| Mutate signals directly | Use `set()` or `update()` |
| Over-globalize state | Keep local when possible |
| Mix RxJS and Signals chaotically | Choose primary, bridge with `toSignal`/`toObservable` |
| Subscribe in components for state | Use template with signals |
---
## Migration Path
### From BehaviorSubject to Signals
```typescript
// Before: RxJS-based
@Injectable({ providedIn: "root" })
export class OldUserService {
private userSubject = new BehaviorSubject<User | null>(null);
user$ = this.userSubject.asObservable();
setUser(user: User) {
this.userSubject.next(user);
}
}
// After: Signal-based
@Injectable({ providedIn: "root" })
export class UserService {
private _user = signal<User | null>(null);
readonly user = this._user.asReadonly();
setUser(user: User) {
this._user.set(user);
}
}
```
### Bridging Signals and RxJS
```typescript
import { toSignal, toObservable } from '@angular/core/rxjs-interop';
// Observable → Signal
@Component({...})
export class ExampleComponent {
private route = inject(ActivatedRoute);
// Convert Observable to Signal
userId = toSignal(
this.route.params.pipe(map(p => p['id'])),
{ initialValue: '' }
);
}
// Signal → Observable
export class DataService {
private filter = signal('');
// Convert Signal to Observable
filter$ = toObservable(this.filter);
filteredData$ = this.filter$.pipe(
debounceTime(300),
switchMap(filter => this.http.get(`/api/data?q=${filter}`))
);
}
```
---
## Resources
- [Angular Signals Guide](https://angular.dev/guide/signals)
- [NgRx Documentation](https://ngrx.io/)
- [NgRx SignalStore](https://ngrx.io/guide/signals)
- [RxAngular](https://www.rx-angular.io/)

View File

@@ -0,0 +1,508 @@
---
name: angular-ui-patterns
description: Modern Angular UI patterns for loading states, error handling, and data display. Use when building UI components, handling async data, or managing component states.
risk: safe
source: self
---
# Angular UI Patterns
## Core Principles
1. **Never show stale UI** - Loading states only when actually loading
2. **Always surface errors** - Users must know when something fails
3. **Optimistic updates** - Make the UI feel instant
4. **Progressive disclosure** - Use `@defer` to show content as available
5. **Graceful degradation** - Partial data is better than no data
---
## Loading State Patterns
### The Golden Rule
**Show loading indicator ONLY when there's no data to display.**
```typescript
@Component({
template: `
@if (error()) {
<app-error-state [error]="error()" (retry)="load()" />
} @else if (loading() && !items().length) {
<app-skeleton-list />
} @else if (!items().length) {
<app-empty-state message="No items found" />
} @else {
<app-item-list [items]="items()" />
}
`,
})
export class ItemListComponent {
private store = inject(ItemStore);
items = this.store.items;
loading = this.store.loading;
error = this.store.error;
}
```
### Loading State Decision Tree
```
Is there an error?
→ Yes: Show error state with retry option
→ No: Continue
Is it loading AND we have no data?
→ Yes: Show loading indicator (spinner/skeleton)
→ No: Continue
Do we have data?
→ Yes, with items: Show the data
→ Yes, but empty: Show empty state
→ No: Show loading (fallback)
```
### Skeleton vs Spinner
| Use Skeleton When | Use Spinner When |
| -------------------- | --------------------- |
| Known content shape | Unknown content shape |
| List/card layouts | Modal actions |
| Initial page load | Button submissions |
| Content placeholders | Inline operations |
---
## Control Flow Patterns
### @if/@else for Conditional Rendering
```html
@if (user(); as user) {
<span>Welcome, {{ user.name }}</span>
} @else if (loading()) {
<app-spinner size="small" />
} @else {
<a routerLink="/login">Sign In</a>
}
```
### @for with Track
```html
@for (item of items(); track item.id) {
<app-item-card [item]="item" (delete)="remove(item.id)" />
} @empty {
<app-empty-state
icon="inbox"
message="No items yet"
actionLabel="Create Item"
(action)="create()"
/>
}
```
### @defer for Progressive Loading
```html
<!-- Critical content loads immediately -->
<app-header />
<app-hero-section />
<!-- Non-critical content deferred -->
@defer (on viewport) {
<app-comments [postId]="postId()" />
} @placeholder {
<div class="h-32 bg-gray-100 animate-pulse"></div>
} @loading (minimum 200ms) {
<app-spinner />
} @error {
<app-error-state message="Failed to load comments" />
}
```
---
## Error Handling Patterns
### Error Handling Hierarchy
```
1. Inline error (field-level) → Form validation errors
2. Toast notification → Recoverable errors, user can retry
3. Error banner → Page-level errors, data still partially usable
4. Full error screen → Unrecoverable, needs user action
```
### Always Show Errors
**CRITICAL: Never swallow errors silently.**
```typescript
// CORRECT - Error always surfaced to user
@Component({...})
export class CreateItemComponent {
private store = inject(ItemStore);
private toast = inject(ToastService);
async create(data: CreateItemDto) {
try {
await this.store.create(data);
this.toast.success('Item created successfully');
this.router.navigate(['/items']);
} catch (error) {
console.error('createItem failed:', error);
this.toast.error('Failed to create item. Please try again.');
}
}
}
// WRONG - Error silently caught
async create(data: CreateItemDto) {
try {
await this.store.create(data);
} catch (error) {
console.error(error); // User sees nothing!
}
}
```
### Error State Component Pattern
```typescript
@Component({
selector: "app-error-state",
standalone: true,
imports: [NgOptimizedImage],
template: `
<div class="error-state">
<img ngSrc="/assets/error-icon.svg" width="64" height="64" alt="" />
<h3>{{ title() }}</h3>
<p>{{ message() }}</p>
@if (retry.observed) {
<button (click)="retry.emit()" class="btn-primary">Try Again</button>
}
</div>
`,
})
export class ErrorStateComponent {
title = input("Something went wrong");
message = input("An unexpected error occurred");
retry = output<void>();
}
```
---
## Button State Patterns
### Button Loading State
```html
<button
(click)="handleSubmit()"
[disabled]="isSubmitting() || !form.valid"
class="btn-primary"
>
@if (isSubmitting()) {
<app-spinner size="small" class="mr-2" />
Saving... } @else { Save Changes }
</button>
```
### Disable During Operations
**CRITICAL: Always disable triggers during async operations.**
```typescript
// CORRECT - Button disabled while loading
@Component({
template: `
<button
[disabled]="saving()"
(click)="save()"
>
@if (saving()) {
<app-spinner size="sm" /> Saving...
} @else {
Save
}
</button>
`
})
export class SaveButtonComponent {
saving = signal(false);
async save() {
this.saving.set(true);
try {
await this.service.save();
} finally {
this.saving.set(false);
}
}
}
// WRONG - User can click multiple times
<button (click)="save()">
{{ saving() ? 'Saving...' : 'Save' }}
</button>
```
---
## Empty States
### Empty State Requirements
Every list/collection MUST have an empty state:
```html
@for (item of items(); track item.id) {
<app-item-card [item]="item" />
} @empty {
<app-empty-state
icon="folder-open"
title="No items yet"
description="Create your first item to get started"
actionLabel="Create Item"
(action)="openCreateDialog()"
/>
}
```
### Contextual Empty States
```typescript
@Component({
selector: "app-empty-state",
template: `
<div class="empty-state">
<span class="icon" [class]="icon()"></span>
<h3>{{ title() }}</h3>
<p>{{ description() }}</p>
@if (actionLabel()) {
<button (click)="action.emit()" class="btn-primary">
{{ actionLabel() }}
</button>
}
</div>
`,
})
export class EmptyStateComponent {
icon = input("inbox");
title = input.required<string>();
description = input("");
actionLabel = input<string | null>(null);
action = output<void>();
}
```
---
## Form Patterns
### Form with Loading and Validation
```typescript
@Component({
template: `
<form [formGroup]="form" (ngSubmit)="onSubmit()">
<div class="form-field">
<label for="name">Name</label>
<input
id="name"
formControlName="name"
[class.error]="isFieldInvalid('name')"
/>
@if (isFieldInvalid("name")) {
<span class="error-text">
{{ getFieldError("name") }}
</span>
}
</div>
<div class="form-field">
<label for="email">Email</label>
<input id="email" type="email" formControlName="email" />
@if (isFieldInvalid("email")) {
<span class="error-text">
{{ getFieldError("email") }}
</span>
}
</div>
<button type="submit" [disabled]="form.invalid || submitting()">
@if (submitting()) {
<app-spinner size="sm" /> Submitting...
} @else {
Submit
}
</button>
</form>
`,
})
export class UserFormComponent {
private fb = inject(FormBuilder);
submitting = signal(false);
form = this.fb.group({
name: ["", [Validators.required, Validators.minLength(2)]],
email: ["", [Validators.required, Validators.email]],
});
isFieldInvalid(field: string): boolean {
const control = this.form.get(field);
return control ? control.invalid && control.touched : false;
}
getFieldError(field: string): string {
const control = this.form.get(field);
if (control?.hasError("required")) return "This field is required";
if (control?.hasError("email")) return "Invalid email format";
if (control?.hasError("minlength")) return "Too short";
return "";
}
async onSubmit() {
if (this.form.invalid) return;
this.submitting.set(true);
try {
await this.service.submit(this.form.value);
this.toast.success("Submitted successfully");
} catch {
this.toast.error("Submission failed");
} finally {
this.submitting.set(false);
}
}
}
```
---
## Dialog/Modal Patterns
### Confirmation Dialog
```typescript
// dialog.service.ts
@Injectable({ providedIn: 'root' })
export class DialogService {
private dialog = inject(Dialog); // CDK Dialog or custom
async confirm(options: {
title: string;
message: string;
confirmText?: string;
cancelText?: string;
}): Promise<boolean> {
const dialogRef = this.dialog.open(ConfirmDialogComponent, {
data: options,
});
return await firstValueFrom(dialogRef.closed) ?? false;
}
}
// Usage
async deleteItem(item: Item) {
const confirmed = await this.dialog.confirm({
title: 'Delete Item',
message: `Are you sure you want to delete "${item.name}"?`,
confirmText: 'Delete',
});
if (confirmed) {
await this.store.delete(item.id);
}
}
```
---
## Anti-Patterns
### Loading States
```typescript
// WRONG - Spinner when data exists (causes flash on refetch)
@if (loading()) {
<app-spinner />
}
// CORRECT - Only show loading without data
@if (loading() && !items().length) {
<app-spinner />
}
```
### Error Handling
```typescript
// WRONG - Error swallowed
try {
await this.service.save();
} catch (e) {
console.log(e); // User has no idea!
}
// CORRECT - Error surfaced
try {
await this.service.save();
} catch (e) {
console.error("Save failed:", e);
this.toast.error("Failed to save. Please try again.");
}
```
### Button States
```html
<!-- WRONG - Button not disabled during submission -->
<button (click)="submit()">Submit</button>
<!-- CORRECT - Disabled and shows loading -->
<button (click)="submit()" [disabled]="loading()">
@if (loading()) {
<app-spinner size="sm" />
} Submit
</button>
```
---
## UI State Checklist
Before completing any UI component:
### UI States
- [ ] Error state handled and shown to user
- [ ] Loading state shown only when no data exists
- [ ] Empty state provided for collections (`@empty` block)
- [ ] Buttons disabled during async operations
- [ ] Buttons show loading indicator when appropriate
### Data & Mutations
- [ ] All async operations have error handling
- [ ] All user actions have feedback (toast/visual)
- [ ] Optimistic updates rollback on failure
### Accessibility
- [ ] Loading states announced to screen readers
- [ ] Error messages linked to form fields
- [ ] Focus management after state changes
---
## Integration with Other Skills
- **angular-state-management**: Use Signal stores for state
- **angular**: Apply modern patterns (Signals, @defer)
- **testing-patterns**: Test all UI states

821
skills/angular/SKILL.md Normal file
View File

@@ -0,0 +1,821 @@
---
name: angular
description: >-
Modern Angular (v20+) expert with deep knowledge of Signals, Standalone
Components, Zoneless applications, SSR/Hydration, and reactive patterns.
Use PROACTIVELY for Angular development, component architecture, state
management, performance optimization, and migration to modern patterns.
risk: safe
source: self
---
# Angular Expert
Master modern Angular development with Signals, Standalone Components, Zoneless applications, SSR/Hydration, and the latest reactive patterns.
## When to Use This Skill
- Building new Angular applications (v20+)
- Implementing Signals-based reactive patterns
- Creating Standalone Components and migrating from NgModules
- Configuring Zoneless Angular applications
- Implementing SSR, prerendering, and hydration
- Optimizing Angular performance
- Adopting modern Angular patterns and best practices
## Do Not Use This Skill When
- Migrating from AngularJS (1.x) → use `angular-migration` skill
- Working with legacy Angular apps that cannot upgrade
- General TypeScript issues → use `typescript-expert` skill
## Instructions
1. Assess the Angular version and project structure
2. Apply modern patterns (Signals, Standalone, Zoneless)
3. Implement with proper typing and reactivity
4. Validate with build and tests
## Safety
- Always test changes in development before production
- Gradual migration for existing apps (don't big-bang refactor)
- Keep backward compatibility during transitions
---
## Angular Version Timeline
| Version | Release | Key Features |
| -------------- | ------- | ------------------------------------------------------ |
| **Angular 20** | Q2 2025 | Signals stable, Zoneless stable, Incremental hydration |
| **Angular 21** | Q4 2025 | Signals-first default, Enhanced SSR |
| **Angular 22** | Q2 2026 | Signal Forms, Selectorless components |
---
## 1. Signals: The New Reactive Primitive
Signals are Angular's fine-grained reactivity system, replacing zone.js-based change detection.
### Core Concepts
```typescript
import { signal, computed, effect } from "@angular/core";
// Writable signal
const count = signal(0);
// Read value
console.log(count()); // 0
// Update value
count.set(5); // Direct set
count.update((v) => v + 1); // Functional update
// Computed (derived) signal
const doubled = computed(() => count() * 2);
// Effect (side effects)
effect(() => {
console.log(`Count changed to: ${count()}`);
});
```
### Signal-Based Inputs and Outputs
```typescript
import { Component, input, output, model } from "@angular/core";
@Component({
selector: "app-user-card",
standalone: true,
template: `
<div class="card">
<h3>{{ name() }}</h3>
<span>{{ role() }}</span>
<button (click)="select.emit(id())">Select</button>
</div>
`,
})
export class UserCardComponent {
// Signal inputs (read-only)
id = input.required<string>();
name = input.required<string>();
role = input<string>("User"); // With default
// Output
select = output<string>();
// Two-way binding (model)
isSelected = model(false);
}
// Usage:
// <app-user-card [id]="'123'" [name]="'John'" [(isSelected)]="selected" />
```
### Signal Queries (ViewChild/ContentChild)
```typescript
import {
Component,
viewChild,
viewChildren,
contentChild,
} from "@angular/core";
@Component({
selector: "app-container",
standalone: true,
template: `
<input #searchInput />
<app-item *ngFor="let item of items()" />
`,
})
export class ContainerComponent {
// Signal-based queries
searchInput = viewChild<ElementRef>("searchInput");
items = viewChildren(ItemComponent);
projectedContent = contentChild(HeaderDirective);
focusSearch() {
this.searchInput()?.nativeElement.focus();
}
}
```
### When to Use Signals vs RxJS
| Use Case | Signals | RxJS |
| ----------------------- | --------------- | -------------------------------- |
| Local component state | ✅ Preferred | Overkill |
| Derived/computed values | ✅ `computed()` | `combineLatest` works |
| Side effects | ✅ `effect()` | `tap` operator |
| HTTP requests | ❌ | ✅ HttpClient returns Observable |
| Event streams | ❌ | ✅ `fromEvent`, operators |
| Complex async flows | ❌ | ✅ `switchMap`, `mergeMap` |
---
## 2. Standalone Components
Standalone components are self-contained and don't require NgModule declarations.
### Creating Standalone Components
```typescript
import { Component } from "@angular/core";
import { CommonModule } from "@angular/common";
import { RouterLink } from "@angular/router";
@Component({
selector: "app-header",
standalone: true,
imports: [CommonModule, RouterLink], // Direct imports
template: `
<header>
<a routerLink="/">Home</a>
<a routerLink="/about">About</a>
</header>
`,
})
export class HeaderComponent {}
```
### Bootstrapping Without NgModule
```typescript
// main.ts
import { bootstrapApplication } from "@angular/platform-browser";
import { provideRouter } from "@angular/router";
import { provideHttpClient } from "@angular/common/http";
import { AppComponent } from "./app/app.component";
import { routes } from "./app/app.routes";
bootstrapApplication(AppComponent, {
providers: [provideRouter(routes), provideHttpClient()],
});
```
### Lazy Loading Standalone Components
```typescript
// app.routes.ts
import { Routes } from "@angular/router";
export const routes: Routes = [
{
path: "dashboard",
loadComponent: () =>
import("./dashboard/dashboard.component").then(
(m) => m.DashboardComponent,
),
},
{
path: "admin",
loadChildren: () =>
import("./admin/admin.routes").then((m) => m.ADMIN_ROUTES),
},
];
```
---
## 3. Zoneless Angular
Zoneless applications don't use zone.js, improving performance and debugging.
### Enabling Zoneless Mode
```typescript
// main.ts
import { bootstrapApplication } from "@angular/platform-browser";
import { provideZonelessChangeDetection } from "@angular/core";
import { AppComponent } from "./app/app.component";
bootstrapApplication(AppComponent, {
providers: [provideZonelessChangeDetection()],
});
```
### Zoneless Component Patterns
```typescript
import { Component, signal, ChangeDetectionStrategy } from "@angular/core";
@Component({
selector: "app-counter",
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<div>Count: {{ count() }}</div>
<button (click)="increment()">+</button>
`,
})
export class CounterComponent {
count = signal(0);
increment() {
this.count.update((v) => v + 1);
// No zone.js needed - Signal triggers change detection
}
}
```
### Key Zoneless Benefits
- **Performance**: No zone.js patches on async APIs
- **Debugging**: Clean stack traces without zone wrappers
- **Bundle size**: Smaller without zone.js (~15KB savings)
- **Interoperability**: Better with Web Components and micro-frontends
---
## 4. Server-Side Rendering & Hydration
### SSR Setup with Angular CLI
```bash
ng add @angular/ssr
```
### Hydration Configuration
```typescript
// app.config.ts
import { ApplicationConfig } from "@angular/core";
import {
provideClientHydration,
withEventReplay,
} from "@angular/platform-browser";
export const appConfig: ApplicationConfig = {
providers: [provideClientHydration(withEventReplay())],
};
```
### Incremental Hydration (v20+)
```typescript
import { Component } from "@angular/core";
@Component({
selector: "app-page",
standalone: true,
template: `
<app-hero />
@defer (hydrate on viewport) {
<app-comments />
}
@defer (hydrate on interaction) {
<app-chat-widget />
}
`,
})
export class PageComponent {}
```
### Hydration Triggers
| Trigger | When to Use |
| ---------------- | --------------------------------------- |
| `on idle` | Low-priority, hydrate when browser idle |
| `on viewport` | Hydrate when element enters viewport |
| `on interaction` | Hydrate on first user interaction |
| `on hover` | Hydrate when user hovers |
| `on timer(ms)` | Hydrate after specified delay |
---
## 5. Modern Routing Patterns
### Functional Route Guards
```typescript
// auth.guard.ts
import { inject } from "@angular/core";
import { Router, CanActivateFn } from "@angular/router";
import { AuthService } from "./auth.service";
export const authGuard: CanActivateFn = (route, state) => {
const auth = inject(AuthService);
const router = inject(Router);
if (auth.isAuthenticated()) {
return true;
}
return router.createUrlTree(["/login"], {
queryParams: { returnUrl: state.url },
});
};
// Usage in routes
export const routes: Routes = [
{
path: "dashboard",
loadComponent: () => import("./dashboard.component"),
canActivate: [authGuard],
},
];
```
### Route-Level Data Resolvers
```typescript
import { inject } from '@angular/core';
import { ResolveFn } from '@angular/router';
import { UserService } from './user.service';
import { User } from './user.model';
export const userResolver: ResolveFn<User> = (route) => {
const userService = inject(UserService);
return userService.getUser(route.paramMap.get('id')!);
};
// In routes
{
path: 'user/:id',
loadComponent: () => import('./user.component'),
resolve: { user: userResolver }
}
// In component
export class UserComponent {
private route = inject(ActivatedRoute);
user = toSignal(this.route.data.pipe(map(d => d['user'])));
}
```
---
## 6. Dependency Injection Patterns
### Modern inject() Function
```typescript
import { Component, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { UserService } from './user.service';
@Component({...})
export class UserComponent {
// Modern inject() - no constructor needed
private http = inject(HttpClient);
private userService = inject(UserService);
// Works in any injection context
users = toSignal(this.userService.getUsers());
}
```
### Injection Tokens for Configuration
```typescript
import { InjectionToken, inject } from "@angular/core";
// Define token
export const API_BASE_URL = new InjectionToken<string>("API_BASE_URL");
// Provide in config
bootstrapApplication(AppComponent, {
providers: [{ provide: API_BASE_URL, useValue: "https://api.example.com" }],
});
// Inject in service
@Injectable({ providedIn: "root" })
export class ApiService {
private baseUrl = inject(API_BASE_URL);
get(endpoint: string) {
return this.http.get(`${this.baseUrl}/${endpoint}`);
}
}
```
---
## 7. Component Composition & Reusability
### Content Projection (Slots)
```typescript
@Component({
selector: 'app-card',
template: `
<div class="card">
<div class="header">
<!-- Select by attribute -->
<ng-content select="[card-header]"></ng-content>
</div>
<div class="body">
<!-- Default slot -->
<ng-content></ng-content>
</div>
</div>
`
})
export class CardComponent {}
// Usage
<app-card>
<h3 card-header>Title</h3>
<p>Body content</p>
</app-card>
```
### Host Directives (Composition)
```typescript
// Reusable behaviors without inheritance
@Directive({
standalone: true,
selector: '[appTooltip]',
inputs: ['tooltip'] // Signal input alias
})
export class TooltipDirective { ... }
@Component({
selector: 'app-button',
standalone: true,
hostDirectives: [
{
directive: TooltipDirective,
inputs: ['tooltip: title'] // Map input
}
],
template: `<ng-content />`
})
export class ButtonComponent {}
```
---
## 8. State Management Patterns
### Signal-Based State Service
```typescript
import { Injectable, signal, computed } from "@angular/core";
interface AppState {
user: User | null;
theme: "light" | "dark";
notifications: Notification[];
}
@Injectable({ providedIn: "root" })
export class StateService {
// Private writable signals
private _user = signal<User | null>(null);
private _theme = signal<"light" | "dark">("light");
private _notifications = signal<Notification[]>([]);
// Public read-only computed
readonly user = computed(() => this._user());
readonly theme = computed(() => this._theme());
readonly notifications = computed(() => this._notifications());
readonly unreadCount = computed(
() => this._notifications().filter((n) => !n.read).length,
);
// Actions
setUser(user: User | null) {
this._user.set(user);
}
toggleTheme() {
this._theme.update((t) => (t === "light" ? "dark" : "light"));
}
addNotification(notification: Notification) {
this._notifications.update((n) => [...n, notification]);
}
}
```
### Component Store Pattern with Signals
```typescript
import { Injectable, signal, computed, inject } from "@angular/core";
import { HttpClient } from "@angular/common/http";
import { toSignal } from "@angular/core/rxjs-interop";
@Injectable()
export class ProductStore {
private http = inject(HttpClient);
// State
private _products = signal<Product[]>([]);
private _loading = signal(false);
private _filter = signal("");
// Selectors
readonly products = computed(() => this._products());
readonly loading = computed(() => this._loading());
readonly filteredProducts = computed(() => {
const filter = this._filter().toLowerCase();
return this._products().filter((p) =>
p.name.toLowerCase().includes(filter),
);
});
// Actions
loadProducts() {
this._loading.set(true);
this.http.get<Product[]>("/api/products").subscribe({
next: (products) => {
this._products.set(products);
this._loading.set(false);
},
error: () => this._loading.set(false),
});
}
setFilter(filter: string) {
this._filter.set(filter);
}
}
```
---
## 9. Forms with Signals (Coming in v22+)
### Current Reactive Forms
```typescript
import { Component, inject } from "@angular/core";
import { FormBuilder, Validators, ReactiveFormsModule } from "@angular/forms";
@Component({
selector: "app-user-form",
standalone: true,
imports: [ReactiveFormsModule],
template: `
<form [formGroup]="form" (ngSubmit)="onSubmit()">
<input formControlName="name" placeholder="Name" />
<input formControlName="email" type="email" placeholder="Email" />
<button [disabled]="form.invalid">Submit</button>
</form>
`,
})
export class UserFormComponent {
private fb = inject(FormBuilder);
form = this.fb.group({
name: ["", Validators.required],
email: ["", [Validators.required, Validators.email]],
});
onSubmit() {
if (this.form.valid) {
console.log(this.form.value);
}
}
}
```
### Signal-Aware Form Patterns (Preview)
```typescript
// Future Signal Forms API (experimental)
import { Component, signal } from '@angular/core';
@Component({...})
export class SignalFormComponent {
name = signal('');
email = signal('');
// Computed validation
isValid = computed(() =>
this.name().length > 0 &&
this.email().includes('@')
);
submit() {
if (this.isValid()) {
console.log({ name: this.name(), email: this.email() });
}
}
}
```
---
## 10. Performance Optimization
### Change Detection Strategies
```typescript
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
// Only checks when:
// 1. Input signal/reference changes
// 2. Event handler runs
// 3. Async pipe emits
// 4. Signal value changes
})
```
### Defer Blocks for Lazy Loading
```typescript
@Component({
template: `
<!-- Immediate loading -->
<app-header />
<!-- Lazy load when visible -->
@defer (on viewport) {
<app-heavy-chart />
} @placeholder {
<div class="skeleton" />
} @loading (minimum 200ms) {
<app-spinner />
} @error {
<p>Failed to load chart</p>
}
`
})
```
### NgOptimizedImage
```typescript
import { NgOptimizedImage } from '@angular/common';
@Component({
imports: [NgOptimizedImage],
template: `
<img
ngSrc="hero.jpg"
width="800"
height="600"
priority
/>
<img
ngSrc="thumbnail.jpg"
width="200"
height="150"
loading="lazy"
placeholder="blur"
/>
`
})
```
---
## 11. Testing Modern Angular
### Testing Signal Components
```typescript
import { ComponentFixture, TestBed } from "@angular/core/testing";
import { CounterComponent } from "./counter.component";
describe("CounterComponent", () => {
let component: CounterComponent;
let fixture: ComponentFixture<CounterComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [CounterComponent], // Standalone import
}).compileComponents();
fixture = TestBed.createComponent(CounterComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it("should increment count", () => {
expect(component.count()).toBe(0);
component.increment();
expect(component.count()).toBe(1);
});
it("should update DOM on signal change", () => {
component.count.set(5);
fixture.detectChanges();
const el = fixture.nativeElement.querySelector(".count");
expect(el.textContent).toContain("5");
});
});
```
### Testing with Signal Inputs
```typescript
import { ComponentFixture, TestBed } from "@angular/core/testing";
import { ComponentRef } from "@angular/core";
import { UserCardComponent } from "./user-card.component";
describe("UserCardComponent", () => {
let fixture: ComponentFixture<UserCardComponent>;
let componentRef: ComponentRef<UserCardComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [UserCardComponent],
}).compileComponents();
fixture = TestBed.createComponent(UserCardComponent);
componentRef = fixture.componentRef;
// Set signal inputs via setInput
componentRef.setInput("id", "123");
componentRef.setInput("name", "John Doe");
fixture.detectChanges();
});
it("should display user name", () => {
const el = fixture.nativeElement.querySelector("h3");
expect(el.textContent).toContain("John Doe");
});
});
```
---
## Best Practices Summary
| Pattern | ✅ Do | ❌ Don't |
| -------------------- | ------------------------------ | ------------------------------- |
| **State** | Use Signals for local state | Overuse RxJS for simple state |
| **Components** | Standalone with direct imports | Bloated SharedModules |
| **Change Detection** | OnPush + Signals | Default CD everywhere |
| **Lazy Loading** | `@defer` and `loadComponent` | Eager load everything |
| **DI** | `inject()` function | Constructor injection (verbose) |
| **Inputs** | `input()` signal function | `@Input()` decorator (legacy) |
| **Zoneless** | Enable for new projects | Force on legacy without testing |
---
## Resources
- [Angular.dev Documentation](https://angular.dev)
- [Angular Signals Guide](https://angular.dev/guide/signals)
- [Angular SSR Guide](https://angular.dev/guide/ssr)
- [Angular Update Guide](https://angular.dev/update-guide)
- [Angular Blog](https://blog.angular.dev)
---
## Common Troubleshooting
| Issue | Solution |
| ------------------------------ | --------------------------------------------------- |
| Signal not updating UI | Ensure `OnPush` + call signal as function `count()` |
| Hydration mismatch | Check server/client content consistency |
| Circular dependency | Use `inject()` with `forwardRef` |
| Zoneless not detecting changes | Trigger via signal updates, not mutations |
| SSR fetch fails | Use `TransferState` or `withFetch()` |

View File

@@ -186,7 +186,7 @@ class CompetitorAnalyzer:
def _analyze_title(self, title: str) -> Dict[str, Any]:
"""Analyze title structure and keyword usage."""
parts = re.split(r'[-:|]', title)
parts = re.split(r'[-' + r':|]', title)
return {
'title': title,

View File

@@ -0,0 +1,70 @@
---
name: computer-vision-expert
description: SOTA Computer Vision Expert (2026). Specialized in YOLO26, Segment Anything 3 (SAM 3), Vision Language Models, and real-time spatial analysis.
---
# Computer Vision Expert (SOTA 2026)
**Role**: Advanced Vision Systems Architect & Spatial Intelligence Expert
## Purpose
To provide expert guidance on designing, implementing, and optimizing state-of-the-art computer vision pipelines. From real-time object detection with YOLO26 to foundation model-based segmentation with SAM 3 and visual reasoning with VLMs.
## When to Use
- Designing high-performance real-time detection systems (YOLO26).
- Implementing zero-shot or text-guided segmentation tasks (SAM 3).
- Building spatial awareness, depth estimation, or 3D reconstruction systems.
- Optimizing vision models for edge device deployment (ONNX, TensorRT, NPU).
- Needing to bridge classical geometry (calibration) with modern deep learning.
## Capabilities
### 1. Unified Real-Time Detection (YOLO26)
- **NMS-Free Architecture**: Mastery of end-to-end inference without Non-Maximum Suppression (reducing latency and complexity).
- **Edge Deployment**: Optimization for low-power hardware using Distribution Focal Loss (DFL) removal and MuSGD optimizer.
- **Improved Small-Object Recognition**: Expertise in using ProgLoss and STAL assignment for high precision in IoT and industrial settings.
### 2. Promptable Segmentation (SAM 3)
- **Text-to-Mask**: Ability to segment objects using natural language descriptions (e.g., "the blue container on the right").
- **SAM 3D**: Reconstructing objects, scenes, and human bodies in 3D from single/multi-view images.
- **Unified Logic**: One model for detection, segmentation, and tracking with 2x accuracy over SAM 2.
### 3. Vision Language Models (VLMs)
- **Visual Grounding**: Leveraging Florence-2, PaliGemma 2, or Qwen2-VL for semantic scene understanding.
- **Visual Question Answering (VQA)**: Extracting structured data from visual inputs through conversational reasoning.
### 4. Geometry & Reconstruction
- **Depth Anything V2**: State-of-the-art monocular depth estimation for spatial awareness.
- **Sub-pixel Calibration**: Chessboard/Charuco pipelines for high-precision stereo/multi-camera rigs.
- **Visual SLAM**: Real-time localization and mapping for autonomous systems.
## Patterns
### 1. Text-Guided Vision Pipelines
- Use SAM 3's text-to-mask capability to isolate specific parts during inspection without needing custom detectors for every variation.
- Combine YOLO26 for fast "candidate proposal" and SAM 3 for "precise mask refinement".
### 2. Deployment-First Design
- Leverage YOLO26's simplified ONNX/TensorRT exports (NMS-free).
- Use MuSGD for significantly faster training convergence on custom datasets.
### 3. Progressive 3D Scene Reconstruction
- Integrate monocular depth maps with geometric homographies to build accurate 2.5D/3D representations of scenes.
## Anti-Patterns
- **Manual NMS Post-processing**: Stick to NMS-free architectures (YOLO26/v10+) for lower overhead.
- **Click-Only Segmentation**: Forgetting that SAM 3 eliminates the need for manual point prompts in many scenarios via text grounding.
- **Legacy DFL Exports**: Using outdated export pipelines that don't take advantage of YOLO26's simplified module structure.
## Sharp Edges (2026)
| Issue | Severity | Solution |
|-------|----------|----------|
| SAM 3 VRAM Usage | Medium | Use quantized/distilled versions for local GPU inference. |
| Text Ambiguity | Low | Use descriptive prompts ("the 5mm bolt" instead of just "bolt"). |
| Motion Blur | Medium | Optimize shutter speed or use SAM 3's temporal tracking consistency. |
| Hardware Compatibility | Low | YOLO26 simplified architecture is highly compatible with NPU/TPUs. |
## Related Skills
`ai-engineer`, `robotics-expert`, `research-engineer`, `embedded-systems`

View File

@@ -197,6 +197,24 @@
"risk": "unknown",
"source": "unknown"
},
{
"id": "angular",
"path": "skills/angular",
"category": "uncategorized",
"name": "angular",
"description": "Modern Angular (v20+) expert with deep knowledge of Signals, Standalone Components, Zoneless applications, SSR/Hydration, and reactive patterns. Use PROACTIVELY for Angular development, component architecture, state management, performance optimization, and migration to modern patterns.",
"risk": "safe",
"source": "self"
},
{
"id": "angular-best-practices",
"path": "skills/angular-best-practices",
"category": "uncategorized",
"name": "angular-best-practices",
"description": "Angular performance optimization and best practices guide. Use when writing, reviewing, or refactoring Angular code for optimal performance, bundle size, and rendering efficiency.",
"risk": "safe",
"source": "self"
},
{
"id": "angular-migration",
"path": "skills/angular-migration",
@@ -206,6 +224,24 @@
"risk": "unknown",
"source": "unknown"
},
{
"id": "angular-state-management",
"path": "skills/angular-state-management",
"category": "uncategorized",
"name": "angular-state-management",
"description": "Master modern Angular state management with Signals, NgRx, and RxJS. Use when setting up global state, managing component stores, choosing between state solutions, or migrating from legacy patterns.",
"risk": "safe",
"source": "self"
},
{
"id": "angular-ui-patterns",
"path": "skills/angular-ui-patterns",
"category": "uncategorized",
"name": "angular-ui-patterns",
"description": "Modern Angular UI patterns for loading states, error handling, and data display. Use when building UI components, handling async data, or managing component states.",
"risk": "safe",
"source": "self"
},
{
"id": "anti-reversing-techniques",
"path": "skills/anti-reversing-techniques",
@@ -1106,6 +1142,15 @@
"risk": "unknown",
"source": "vibeship-spawner-skills (Apache 2.0)"
},
{
"id": "computer-vision-expert",
"path": "skills/computer-vision-expert",
"category": "uncategorized",
"name": "computer-vision-expert",
"description": "SOTA Computer Vision Expert (2026). Specialized in YOLO26, Segment Anything 3 (SAM 3), Vision Language Models, and real-time spatial analysis.",
"risk": "unknown",
"source": "unknown"
},
{
"id": "concise-planning",
"path": "skills/concise-planning",