Agent Skill
2/7/2026

angular-21-conventions

This skill should be used when working with Angular 21+ codebases, when the user asks about "Angular components", "standalone components", "Angular signals", "inject() function", "Angular dependency injection", "Angular templates", "Angular services", or when EREN detects an Angular project. Provides modern Angular 21+ patterns and best practices.

U
ugurckan
2GitHub Stars
1Views
npx skills add ugurckan/eren

SKILL.md

Nameangular-21-conventions
DescriptionThis skill should be used when working with Angular 21+ codebases, when the user asks about "Angular components", "standalone components", "Angular signals", "inject() function", "Angular dependency injection", "Angular templates", "Angular services", or when EREN detects an Angular project. Provides modern Angular 21+ patterns and best practices.

name: Angular 21+ Conventions description: This skill should be used when working with Angular 21+ codebases, when the user asks about "Angular components", "standalone components", "Angular signals", "inject() function", "Angular dependency injection", "Angular templates", "Angular services", or when EREN detects an Angular project. Provides modern Angular 21+ patterns and best practices. version: 0.1.0

Angular 21+ Conventions

Modern Angular (version 21+) has evolved significantly from earlier versions. This skill provides decision trees and patterns for working with Angular 21+ codebases effectively.

Core Principles

Angular 21+ emphasizes:

  • Standalone components as the default (no NgModules)
  • Signals for reactive state management
  • Functional APIs over class decorators where possible
  • inject() function for dependency injection
  • Zoneless change detection (optional but recommended)

Standalone Components

All components in Angular 21+ should be standalone by default.

Component Structure

import { Component, signal, computed, inject } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterLink } from '@angular/router';

@Component({
  selector: 'app-user-profile',
  standalone: true,
  imports: [CommonModule, RouterLink],
  template: `
    <div class="profile">
      <h2>{{ user().name }}</h2>
      <p>{{ formattedEmail() }}</p>
      <a routerLink="/settings">Settings</a>
    </div>
  `
})
export class UserProfileComponent {
  private userService = inject(UserService);

  user = signal<User | null>(null);
  formattedEmail = computed(() => this.user()?.email?.toLowerCase() ?? '');

  constructor() {
    this.loadUser();
  }

  private async loadUser() {
    this.user.set(await this.userService.getCurrentUser());
  }
}

Decision Tree: Component Creation

  1. Is this a new component? → Use standalone: true (always)
  2. Does it need other components? → Add to imports array
  3. Does it need services? → Use inject() function
  4. Does it manage state? → Use signals
  5. Is state derived from other state? → Use computed()

Signals

Signals are the primary state management primitive in Angular 21+.

Signal Types

TypeUse CaseExample
signal<T>()Writable statecount = signal(0)
computed()Derived statedoubled = computed(() => this.count() * 2)
effect()Side effectseffect(() => console.log(this.count()))

Signal Patterns

// Basic signal
name = signal('');

// Signal with explicit type
items = signal<Item[]>([]);

// Computed signal (read-only, auto-tracks dependencies)
itemCount = computed(() => this.items().length);
isEmpty = computed(() => this.items().length === 0);

// Updating signals
this.name.set('New Name');
this.items.update(items => [...items, newItem]);

// Effects for side effects
effect(() => {
  localStorage.setItem('items', JSON.stringify(this.items()));
});

Decision Tree: State Management

  1. Is this simple component state? → Use signal()
  2. Is this derived from other signals? → Use computed()
  3. Need to react to signal changes? → Use effect()
  4. Is this shared across components? → Create a signal-based service
  5. Is this complex global state? → Consider NgRx with SignalStore

Dependency Injection with inject()

The inject() function replaces constructor injection for cleaner code.

Pattern

@Component({...})
export class MyComponent {
  // Preferred: inject() function
  private readonly userService = inject(UserService);
  private readonly router = inject(Router);
  private readonly route = inject(ActivatedRoute);

  // Optional injection
  private readonly analytics = inject(AnalyticsService, { optional: true });
}

Decision Tree: Service Injection

  1. Is this a required dependency?inject(ServiceName)
  2. Is this optional?inject(ServiceName, { optional: true })
  3. Need to inject in a function? → Inject at class level, use in function
  4. Creating a service? → Use providedIn: 'root' for singletons

Services

Services use providedIn for tree-shakeable singletons.

Service Pattern

import { Injectable, signal, computed } from '@angular/core';

@Injectable({ providedIn: 'root' })
export class UserService {
  private currentUser = signal<User | null>(null);

  readonly user = this.currentUser.asReadonly();
  readonly isLoggedIn = computed(() => this.currentUser() !== null);

  async login(credentials: Credentials): Promise<void> {
    const user = await this.authApi.login(credentials);
    this.currentUser.set(user);
  }

  logout(): void {
    this.currentUser.set(null);
  }
}

Routing

Angular 21+ uses functional route guards and resolvers.

Route Configuration

export const routes: Routes = [
  {
    path: 'dashboard',
    loadComponent: () => import('./dashboard.component').then(m => m.DashboardComponent),
    canActivate: [() => inject(AuthService).isLoggedIn()]
  },
  {
    path: 'users/:id',
    loadComponent: () => import('./user.component').then(m => m.UserComponent),
    resolve: {
      user: () => inject(UserService).getUser(inject(ActivatedRoute).snapshot.params['id'])
    }
  }
];

Template Syntax

Control Flow (New Syntax)

Angular 21+ uses built-in control flow instead of *ngIf/*ngFor:

@if (user()) {
  <app-user-card [user]="user()" />
} @else {
  <p>Loading...</p>
}

@for (item of items(); track item.id) {
  <app-item [item]="item" />
} @empty {
  <p>No items found</p>
}

@switch (status()) {
  @case ('loading') { <spinner /> }
  @case ('error') { <error-message /> }
  @default { <content /> }
}

Decision Tree: Template Syntax

  1. Conditional rendering? → Use @if/@else
  2. Iterating a list? → Use @for with track
  3. Multiple conditions? → Use @switch/@case
  4. Need legacy directives? → Import from CommonModule (but prefer new syntax)

File Organization

Recommended Structure

src/app/
├── core/                    # Singleton services, guards
│   ├── services/
│   │   ├── auth.service.ts
│   │   └── api.service.ts
│   └── guards/
├── features/                # Feature modules (lazy-loaded)
│   ├── users/
│   │   ├── components/
│   │   │   ├── user-list.component.ts
│   │   │   └── user-detail.component.ts
│   │   ├── services/
│   │   │   └── user.service.ts
│   │   └── users.routes.ts
│   └── dashboard/
├── shared/                  # Shared components, pipes, directives
│   ├── components/
│   ├── pipes/
│   └── directives/
└── app.routes.ts           # Root routes

Common Patterns

Input/Output with Signals

@Component({...})
export class ChildComponent {
  // Input as signal
  name = input<string>('');
  required = input.required<number>();

  // Output
  save = output<User>();

  onSave() {
    this.save.emit(this.buildUser());
  }
}

Two-Way Binding

// Component
@Component({...})
export class CounterComponent {
  value = model(0);  // Creates value and valueChange
}

// Usage
<app-counter [(value)]="count" />

Additional Resources

Reference Files

For detailed patterns and migration guides:

  • references/signal-patterns.md - Advanced signal patterns
  • references/migration-guide.md - Migrating from older Angular versions
  • references/testing-patterns.md - Testing Angular 21+ components

Key Points

  • Always use standalone components
  • Prefer signals over BehaviorSubject/Observable for component state
  • Use inject() instead of constructor injection
  • Use new control flow syntax (@if, @for, @switch)
  • Lazy load feature routes with loadComponent
Skills Info
Original Name:angular-21-conventionsAuthor:ugurckan