Agent skill
scope-rule-architect-angular
Angular 20+ architecture with Scope Rule, Screaming Architecture, standalone components, and signals. Trigger: When writing Angular components, services, templates, or making architectural decisions about component placement.
Install this agent skill to your Project
npx add-skill https://github.com/Gentleman-Programming/Gentleman.Dots/tree/main/GentlemanOpenCode/skills/angular
Metadata
Additional technical details for this skill
- author
- gentleman-programming
- version
- 1.0
SKILL.md
Core Angular 20 Principles
1. Standalone Components First
- ALL components MUST be standalone — never use NgModules for feature organization, since Angular 20 ALL components are standalone by default and don't need
standalone: true - Use
input()andoutput()functions instead of decorators - Implement
ChangeDetectionStrategy.OnPushfor all components - Use
inject()instead of constructor injection - Don't use
any - Don't use lifecycle hooks like
ngOnInit— use signals and computed instead - Leverage signals for state management with
signal(),computed(), andeffect()
2. Modern Template Syntax
- Use native control flow (
@if,@for,@switch) instead of structural directives - Use
@deferfor lazy loading content and performance - Prefer
classandstylebindings overngClassandngStyle - Use
NgOptimizedImagefor all static images - Implement reactive forms over template-driven forms
- Use typed reactive forms
- No
.component,.service,.modulesuffixes in filenames — the name should tell the behavior
3. The Scope Rule — Unbreakable Law
"Scope determines structure"
- Code used by 2+ features → MUST go in global/shared directories
- Code used by 1 feature → MUST stay local in that feature
- NO EXCEPTIONS
4. Screaming Architecture
- Feature names must describe business functionality, not technical implementation
- Directory structure should tell the story of what the app does at first glance
- Main feature components MUST have the same name as their feature
Decision Framework
- Count usage: Identify exactly how many features use the component
- Apply the rule: 1 feature = local placement, 2+ features = shared/global
- Validate against best practices: Ensure compliance with Angular patterns
- Document decision: Explain WHY the placement was chosen
Project Structure
src/
app/
features/
[feature-name]/
[feature-name].ts # Main standalone component
components/ # Feature-specific standalone components
[component-name].ts
services/ # Feature-specific services with inject()
[service-name].ts
guards/ # Feature-specific guards
models/ # Feature-specific interfaces/types
signals/ # Feature-specific signal stores
shared/ # ONLY for 2+ feature usage
components/ # Shared standalone components
services/ # Shared services
guards/ # Shared guards
pipes/ # Shared pipes
directives/ # Shared directives
core/ # Singleton services and app-wide concerns
services/
auth.ts
api.ts
interceptors/
guards/
main.ts # Bootstrap with standalone component
app.config.ts # App configuration
app.ts # Root standalone component
routes.ts # Route configuration
Path Aliases (tsconfig.json)
{
"paths": {
"@features/*": ["src/app/features/*"],
"@shared/*": ["src/app/features/shared/*"],
"@core/*": ["src/app/core/*"]
}
}
Standalone Component Pattern
import {
Component,
ChangeDetectionStrategy,
signal,
computed,
input,
output,
inject,
} from "@angular/core";
@Component({
selector: "app-feature-name",
imports: [/* required dependencies */],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
@if (isLoading()) {
<div>Loading...</div>
} @else {
@for (item of items(); track item.id) {
<div>{{ item.name }}</div>
}
}
`,
})
export class FeatureNameComponent {
// Use input() function instead of @Input()
readonly data = input<DataType>();
readonly config = input({ required: true });
// Use output() function instead of @Output()
readonly itemSelected = output<ItemType>();
// Use signals for state
private readonly loading = signal(false);
readonly isLoading = this.loading.asReadonly();
// Use computed for derived state
readonly items = computed(
() => this.data()?.filter((item) => item.active) ?? [],
);
// Use inject() instead of constructor injection
private readonly service = inject(FeatureService);
}
Service with Signals
import { Injectable, signal, computed, inject } from "@angular/core";
@Injectable({
providedIn: "root",
})
export class FeatureService {
private readonly http = inject(HttpClient);
// Private signals for internal state
private readonly _state = signal<FeatureState>({
items: [],
loading: false,
error: null,
});
// Public readonly computed values
readonly items = computed(() => this._state().items);
readonly loading = computed(() => this._state().loading);
readonly error = computed(() => this._state().error);
loadItems(): void {
this._state.update((state) => ({ ...state, loading: true }));
// Implementation
}
}
Quality Checklist
- Scope verification: Have you correctly counted feature usage?
- Angular compliance: Are you using standalone components and modern patterns?
- Naming validation: Do component names match feature names and follow Angular conventions?
- Screaming test: Can a new Angular developer understand what the app does from the structure alone?
- Signal usage: Are you leveraging signals appropriately for state management?
- Future-proofing: Will this structure scale with Angular's evolution?
Edge Cases
- Legacy NgModule migration: Always convert to standalone components during restructuring
- Lazy loading: Use standalone component routes instead of module-based lazy loading
- Signal stores: Place feature-specific signals locally, shared signals globally
- Service scope: Use
providedIn: 'root'for shared services, local provision for feature-specific services - Form handling: Implement reactive forms with signals for state management
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
django-drf
Django REST Framework patterns. Trigger: When building REST APIs with Django - ViewSets, Serializers, Filters.
homebrew-release
Release workflow for Gentleman-Programming homebrew-tap projects (GGA, Gentleman.Dots). Trigger: When user asks to release, bump version, update homebrew, or publish a new version.
technical-review
Review technical exercises and candidate submissions with structured evaluation. Trigger: When reviewing technical exercises, code assessments, candidate submissions, or take-home tests.
sdd-init
Initialize Spec-Driven Development context in any project. Detects stack, conventions, and bootstraps the active persistence backend. Trigger: When user wants to initialize SDD in a project, or says "sdd init", "iniciar sdd", "openspec init".
zod-4
Zod 4 schema validation patterns. Trigger: When using Zod for validation - breaking changes from v3.
skill-registry
Create or update the skill registry for the current project. Scans user skills and project conventions, writes .atl/skill-registry.md, and saves to engram if available. Trigger: When user says "update skills", "skill registry", "actualizar skills", "update registry", or after installing/removing skills.
Didn't find tool you were looking for?