# Content Projection Patterns

Advanced `ng-content`, `ng-template`, and `ng-container` techniques.

## Basic Projection

### Single Slot

```typescript
@Component({
  selector: 'ui-panel',
  template: `<div class="panel"><ng-content></ng-content></div>`
})
export class PanelComponent {}
```

### Multi-Slot with Selectors

```typescript
@Component({
  selector: 'ui-card',
  template: `
    <header><ng-content select="card-header"></ng-content></header>
    <main><ng-content select="card-body"></ng-content></main>
    <footer><ng-content></ng-content></footer> <!-- default -->
  `
})
```

**Usage:**
```html
<ui-card>
  <card-header><h3>Title</h3></card-header>
  <card-body><p>Content</p></card-body>
  <button>Action</button> <!-- default slot -->
</ui-card>
```

**Selectors:** Element (`card-title`), class (`.actions`), attribute (`[slot='footer']`)
**Fallback:** `<ng-content select="title">Default</ng-content>`
**Aliasing:** `<h3 ngProjectAs="card-header">Title</h3>`

## Conditional Projection

`ng-content` always instantiates (even if hidden). Use `ng-template` for truly conditional content:

```typescript
@Component({
  selector: 'ui-expandable',
  imports: [NgTemplateOutlet],
  template: `
    <div (click)="toggle()">
      <ng-content select="header"></ng-content>
      <span>{{ isExpanded() ? '▼' : '▶' }}</span>
    </div>
    @if (isExpanded()) {
      <ng-container *ngTemplateOutlet="contentTemplate()"></ng-container>
    }
  `
})
export class ExpandableComponent {
  isExpanded = signal(false);
  contentTemplate = contentChild<TemplateRef<unknown>>('content');
  toggle() { this.isExpanded.update(v => !v); }
}
```

**Usage:**
```html
<ui-expandable>
  <header><h3>Click to expand</h3></header>
  <ng-template #content>
    <app-heavy-component /> <!-- Only rendered when expanded -->
  </ng-template>
</ui-expandable>
```

## Template-Based Projection

### Accepting Template Fragments

```typescript
@Component({
  selector: 'ui-list',
  imports: [NgTemplateOutlet],
  template: `
    <ul>
      @for (item of items(); track item.id) {
        <li>
          <ng-container
            *ngTemplateOutlet="itemTemplate(); context: { $implicit: item, index: $index }">
          </ng-container>
        </li>
      }
    </ul>
  `
})
export class ListComponent<T> {
  items = input.required<T[]>();
  itemTemplate = contentChild.required<TemplateRef<{ $implicit: T; index: number }>>('itemTemplate');
}
```

**Usage:**
```html
<ui-list [items]="users()">
  <ng-template #itemTemplate let-user let-i="index">
    <div>{{i + 1}}. {{user.name}}</div>
  </ng-template>
</ui-list>
```

### Multiple Template Slots

```typescript
@Component({
  selector: 'ui-data-table',
  imports: [NgTemplateOutlet],
  template: `
    <table>
      <thead><ng-container *ngTemplateOutlet="headerTemplate()"></ng-container></thead>
      <tbody>
        @for (row of data(); track row.id) {
          <ng-container *ngTemplateOutlet="rowTemplate(); context: { $implicit: row }"></ng-container>
        }
      </tbody>
      <tfoot><ng-container *ngTemplateOutlet="footerTemplate()"></ng-container></tfoot>
    </table>
  `
})
export class DataTableComponent<T> {
  data = input.required<T[]>();
  headerTemplate = contentChild.required<TemplateRef<void>>('header');
  rowTemplate = contentChild.required<TemplateRef<{ $implicit: T }>>('row');
  footerTemplate = contentChild<TemplateRef<void>>('footer');
}
```

## Querying Projected Content

### Using ContentChildren

```typescript
@Component({
  selector: 'ui-tabs',
  template: `
    <nav>
      @for (tab of tabs(); track tab.id) {
        <button [class.active]="tab === activeTab()" (click)="selectTab(tab)">
          {{tab.label()}}
        </button>
      }
    </nav>
    <ng-content></ng-content>
  `
})
export class TabsComponent {
  tabs = contentChildren(TabComponent);
  activeTab = signal<TabComponent | null>(null);

  ngAfterContentInit() {
    this.selectTab(this.tabs()[0]);
  }

  selectTab(tab: TabComponent) {
    this.tabs().forEach(t => t.isActive.set(false));
    tab.isActive.set(true);
    this.activeTab.set(tab);
  }
}

@Component({
  selector: 'ui-tab',
  template: `@if (isActive()) { <ng-content></ng-content> }`
})
export class TabComponent {
  label = input.required<string>();
  isActive = signal(false);
  id = Math.random().toString(36);
}
```

## Real-World Examples

### Modal/Dialog

```typescript
@Component({
  selector: 'ui-modal',
  imports: [NgTemplateOutlet],
  template: `
    @if (isOpen()) {
      <div class="backdrop" (click)="close()">
        <div class="modal" (click)="$event.stopPropagation()">
          <header>
            <ng-content select="modal-title"></ng-content>
            <button (click)="close()">×</button>
          </header>
          <main><ng-content></ng-content></main>
          <footer>
            <ng-content select="modal-actions">
              <button (click)="close()">Close</button>
            </ng-content>
          </footer>
        </div>
      </div>
    }
  `
})
export class ModalComponent {
  isOpen = signal(false);
  open() { this.isOpen.set(true); }
  close() { this.isOpen.set(false); }
}
```

### Form Field Wrapper

```typescript
@Component({
  selector: 'ui-form-field',
  template: `
    <div class="form-field" [class.has-error]="error()">
      <label [for]="fieldId()">
        <ng-content select="field-label"></ng-content>
        @if (required()) { <span class="required">*</span> }
      </label>
      <div class="input-wrapper"><ng-content></ng-content></div>
      @if (error()) { <span class="error">{{error()}}</span> }
      @if (hint()) { <span class="hint">{{hint()}}</span> }
    </div>
  `
})
export class FormFieldComponent {
  fieldId = input.required<string>();
  required = input(false);
  error = input<string>();
  hint = input<string>();
}
```

## Performance Notes

- `ng-content` **always instantiates** projected content
- For conditional projection, use `ng-template` + `NgTemplateOutlet`
- Projected content evaluates in **parent component context**
- Use `computed()` for expensive expressions in projected content

## Common Pitfalls

1. **Using ng-content in structural directives:** Won't work as expected. Use `ng-template` instead.
2. **Forgetting default slot:** Unmatched content disappears without default `<ng-content></ng-content>`
3. **Template order matters:** Content renders in template order, not usage order
