Files
ISA-Frontend/.claude/skills/angular-template/references/control-flow-reference.md
Lorenz Hilpert 6f238816ef feat: add Angular template skill for modern template patterns
Add comprehensive skill for Angular 20+ template best practices covering:
- Modern control flow (@if, @for, @switch, @defer)
- Content projection (ng-content)
- Template references (ng-template, ng-container)
- Variable declarations (@let)
- Expression binding patterns
- Performance optimization strategies
- Migration guides from legacy syntax

Includes 4 reference files with detailed examples:
- control-flow-reference.md: Advanced @if/@for/@switch patterns
- defer-patterns.md: Lazy loading and Core Web Vitals optimization
- projection-patterns.md: ng-content advanced techniques
- template-reference.md: ng-template/ng-container usage

All files optimized for context efficiency (~65% reduction from initial draft)
while preserving all essential patterns, best practices, and examples.
2025-10-23 20:42:28 +02:00

3.5 KiB

Control Flow Reference

Advanced patterns for @if, @for, @switch.

@if Patterns

Store Results with as

@if (user.profile?.settings?.theme; as theme) {
  <p>Theme: {{theme}}</p>
}

@if (data$ | async; as data) {
  <app-list [items]="data.items" />
}

Complex Conditions

@if (isAdmin() && hasPermission('edit')) {
  <button (click)="edit()">Edit</button>
}

@if (user()?.role === 'admin' || user()?.role === 'moderator') {
  <app-moderation-panel />
}

@for Patterns

Contextual Variables

@for (user of users(); track user.id) {
  <tr [class.odd]="$odd">
    <td>{{$index + 1}}</td>
    <td>{{user.name}}</td>
    <td>{{$count}} total</td>
    @if ($first) { <span>First</span> }
  </tr>
}

Track Strategies

// ✅ Best: Unique ID
@for (order of orders(); track order.uuid) { }

// ✅ Good: Composite key
@for (item of items(); track item.categoryId + '-' + item.id) { }

// ⚠️ OK: Index (static only)
@for (color of ['red', 'blue']; track $index) { }

// ❌ NEVER: Identity function
@for (item of items(); track identity(item)) { }

Nested Loops

@for (category of categories(); track category.id) {
  <div class="category">
    <h3>{{category.name}}</h3>
    @for (product of category.products; track product.id) {
      <app-product [product]="product" [categoryIdx]="$index" />
    }
  </div>
}

Filter in Component

// Component
activeUsers = computed(() => this.users().filter(u => u.isActive));

// Template
@for (user of activeUsers(); track user.id) {
  <app-user-card [user]="user" />
} @empty {
  <p>No active users</p>
}

@switch Patterns

Basic Switch

@switch (viewMode()) {
  @case ('grid') { <app-grid-view /> }
  @case ('list') { <app-list-view /> }
  @case ('table') { <app-table-view /> }
  @default { <app-grid-view /> }
}

Nested Switch

@switch (category()) {
  @case ('electronics') {
    @switch (subcategory()) {
      @case ('phones') { <app-phone-list /> }
      @case ('laptops') { <app-laptop-list /> }
      @default { <app-electronics-list /> }
    }
  }
  @case ('clothing') { <app-clothing-list /> }
  @default { <app-all-categories /> }
}

Combined with Other Control Flow

@switch (status()) {
  @case ('loading') { <mat-spinner /> }
  @case ('success') {
    @if (data()?.length) {
      @for (item of data(); track item.id) {
        <app-item [item]="item" />
      }
    } @else {
      <p>No data</p>
    }
  }
  @case ('error') { <app-error [message]="errorMessage()" /> }
}

Common Patterns

Loading State

@if (isLoading()) {
  <mat-spinner />
} @else if (error()) {
  <app-error [error]="error()" (retry)="loadData()" />
} @else {
  @for (item of items(); track item.id) {
    <app-item [item]="item" />
  } @empty {
    <app-empty-state />
  }
}

Tab Navigation

<nav>
  @for (tab of tabs(); track tab.id) {
    <button [class.active]="activeTab() === tab.id" (click)="setActiveTab(tab.id)">
      {{tab.label}}
    </button>
  }
</nav>

@switch (activeTab()) {
  @case ('profile') { <app-profile /> }
  @case ('settings') { <app-settings /> }
  @default { <app-profile /> }
}

Hierarchical Data

@for (section of sections(); track section.id) {
  <details [open]="section.isExpanded">
    <summary>{{section.title}} ({{section.items.length}})</summary>
    @for (item of section.items; track item.id) {
      <div>{{item.name}}</div>
    } @empty {
      <p>No items</p>
    }
  </details>
}