# @isa/common/title-management

> Reusable title management patterns for Angular applications with reactive updates and tab integration.

## Overview

This library provides two complementary approaches for managing page titles in the ISA application:

1. **`IsaTitleStrategy`** - A custom TitleStrategy for route-based static titles
2. **`usePageTitle()`** - A reactive helper function for component-based dynamic titles

Both approaches automatically:
- Add the ISA prefix from config to all titles
- Update the TabService for multi-tab navigation
- Set the browser document title

## When to Use What

| Scenario | Recommended Approach |
|----------|---------------------|
| Static page title (never changes) | Route configuration with `IsaTitleStrategy` |
| Dynamic title based on user input (search, filters) | `usePageTitle()` in component |
| Title depends on loaded data (item name, ID) | `usePageTitle()` in component |
| Wizard/multi-step flows with changing steps | `usePageTitle()` in component |
| Combination of static base + dynamic suffix | Both (route + `usePageTitle()`) |

## Installation

This library is already installed and configured in your workspace. Import from:

```typescript
import { IsaTitleStrategy, usePageTitle } from '@isa/common/title-management';
```

## Setup

### 1. Configure IsaTitleStrategy in AppModule

To enable automatic title management for all routes, add the `IsaTitleStrategy` to your app providers:

```typescript
// apps/isa-app/src/app/app.module.ts
import { TitleStrategy } from '@angular/router';
import { IsaTitleStrategy } from '@isa/common/title-management';

@NgModule({
  providers: [
    { provide: TitleStrategy, useClass: IsaTitleStrategy }
  ]
})
export class AppModule {}
```

**Note:** This replaces Angular's default `TitleStrategy` with our custom implementation that adds the ISA prefix and updates tabs.

## Usage

### Static Titles (Route Configuration)

For pages with fixed titles, simply add a `title` property to your route:

```typescript
// In your routing module
const routes: Routes = [
  {
    path: 'dashboard',
    component: DashboardComponent,
    title: 'Dashboard' // Will become "ISA - Dashboard"
  },
  {
    path: 'artikelsuche',
    component: ArticleSearchComponent,
    title: 'Artikelsuche' // Will become "ISA - Artikelsuche"
  },
  {
    path: 'returns',
    component: ReturnsComponent,
    title: 'Rückgaben' // Will become "ISA - Rückgaben"
  }
];
```

The `IsaTitleStrategy` will automatically:
- Add the configured prefix (default: "ISA")
- Update the active tab name
- Set the document title

### Dynamic Titles (Component with Signals)

For pages where the title depends on component state, use the `usePageTitle()` helper:

#### Example 1: Search Page with Query Term

```typescript
import { Component, signal, computed } from '@angular/core';
import { usePageTitle } from '@isa/common/title-management';

@Component({
  selector: 'app-article-search',
  standalone: true,
  template: `
    <input [(ngModel)]="searchTerm" placeholder="Search..." />
    <h1>{{ pageTitle().title }}</h1>
  `
})
export class ArticleSearchComponent {
  searchTerm = signal('');

  // Computed signal that updates when searchTerm changes
  pageTitle = computed(() => {
    const term = this.searchTerm();
    return {
      title: term ? `Artikelsuche - "${term}"` : 'Artikelsuche'
    };
  });

  constructor() {
    // Title updates automatically when searchTerm changes!
    usePageTitle(this.pageTitle);
  }
}
```

**Result:**
- Initial load: `ISA - Artikelsuche`
- After searching "Laptop": `ISA - Artikelsuche - "Laptop"`
- Tab name also updates automatically

#### Example 2: Detail Page with Item Name

```typescript
import { Component, signal, computed, inject, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { usePageTitle } from '@isa/common/title-management';

@Component({
  selector: 'app-product-details',
  standalone: true,
  template: `<h1>{{ productName() || 'Loading...' }}</h1>`
})
export class ProductDetailsComponent implements OnInit {
  private route = inject(ActivatedRoute);

  productName = signal<string | null>(null);

  pageTitle = computed(() => {
    const name = this.productName();
    return {
      title: name ? `Produkt - ${name}` : 'Produkt Details'
    };
  });

  constructor() {
    usePageTitle(this.pageTitle);
  }

  ngOnInit() {
    // Load product data...
    this.productName.set('Samsung Galaxy S24');
  }
}
```

#### Example 3: Combining Route Title with Dynamic Content

```typescript
import { Component, signal, computed, inject } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { usePageTitle } from '@isa/common/title-management';

@Component({
  selector: 'app-order-wizard',
  standalone: true,
  template: `<div>Step {{ currentStep() }} of 3</div>`
})
export class OrderWizardComponent {
  private route = inject(ActivatedRoute);

  currentStep = signal(1);

  pageTitle = computed(() => {
    // Get base title from route config
    const baseTitle = this.route.snapshot.title || 'Bestellung';
    const step = this.currentStep();
    return {
      title: `${baseTitle} - Schritt ${step}/3`
    };
  });

  constructor() {
    usePageTitle(this.pageTitle);
  }
}
```

### Nested Components (Parent/Child Routes)

When using `usePageTitle()` in nested component hierarchies (parent/child routes), the **deepest component automatically wins**. When the child component is destroyed (e.g., navigating away), the parent's title is automatically restored.

**This happens automatically** - no configuration or depth tracking needed!

#### Example: Dashboard → Settings Flow

```typescript
// Parent route: /dashboard
@Component({
  selector: 'app-dashboard',
  standalone: true,
  template: `<router-outlet />`
})
export class DashboardComponent {
  pageTitle = signal({ title: 'Dashboard' });

  constructor() {
    usePageTitle(this.pageTitle);
    // Sets: "ISA - Dashboard"
  }
}

// Child route: /dashboard/settings
@Component({
  selector: 'app-settings',
  standalone: true,
  template: `<h1>Settings</h1>`
})
export class SettingsComponent {
  pageTitle = signal({ title: 'Settings' });

  constructor() {
    usePageTitle(this.pageTitle);
    // Sets: "ISA - Settings" (child wins!)
  }
}

// Navigation flow:
// 1. Navigate to /dashboard → Title: "ISA - Dashboard"
// 2. Navigate to /dashboard/settings → Title: "ISA - Settings" (child takes over)
// 3. Navigate back to /dashboard → Title: "ISA - Dashboard" (parent restored automatically)
```

#### How It Works

The library uses an internal registry that tracks component creation order:
- **Last-registered (deepest) component controls the title**
- **Parent components' title updates are ignored** while child is active
- **Automatic cleanup** via Angular's `DestroyRef` - when child is destroyed, parent becomes active again

#### Real-World Scenario

```typescript
// Main page with search
@Component({...})
export class ArticleSearchComponent {
  searchTerm = signal('');

  pageTitle = computed(() => {
    const term = this.searchTerm();
    return {
      title: term ? `Artikelsuche - "${term}"` : 'Artikelsuche'
    };
  });

  constructor() {
    usePageTitle(this.pageTitle); // "ISA - Artikelsuche"
  }
}

// Detail view (child route)
@Component({...})
export class ArticleDetailsComponent {
  articleName = signal('Samsung Galaxy S24');

  pageTitle = computed(() => ({
    title: `Artikel - ${this.articleName()}`
  }));

  constructor() {
    usePageTitle(this.pageTitle); // "ISA - Artikel - Samsung Galaxy S24" (wins!)
  }
}

// When user closes detail view → "ISA - Artikelsuche" is restored automatically
```

#### Important Notes

✅ **Works with any nesting depth** - Grandparent → Parent → Child → Grandchild

✅ **No manual depth tracking** - Registration order determines precedence

✅ **Automatic restoration** - Parent title restored when child is destroyed

⚠️ **Parent signal updates are ignored** while child is active (by design!)

### Tab Subtitles

You can include a subtitle in the signal to display additional context in the tab:

```typescript
constructor() {
  this.pageTitle = signal({
    title: 'Dashboard',
    subtitle: 'Active Orders'
  });
  usePageTitle(this.pageTitle);
}
```

**Use Cases for Subtitles:**
- **Status indicators**: `"Pending"`, `"Active"`, `"Completed"`
- **Context information**: `"3 items"`, `"Last updated: 2min ago"`
- **Category labels**: `"Customer"`, `"Order"`, `"Product"`
- **Step indicators**: `"Step 2 of 5"`, `"Review"`

#### Example: Order Processing with Status

```typescript
@Component({
  selector: 'app-order-details',
  standalone: true,
  template: `
    <h1>Order {{ orderId() }}</h1>
    <p>Status: {{ orderStatus() }}</p>
  `
})
export class OrderDetailsComponent {
  orderId = signal('12345');
  orderStatus = signal<'pending' | 'processing' | 'complete'>('pending');

  // Status labels for subtitle
  statusLabels = {
    pending: 'Awaiting Payment',
    processing: 'In Progress',
    complete: 'Completed'
  };

  pageTitle = computed(() => ({
    title: `Order ${this.orderId()}`,
    subtitle: this.statusLabels[this.orderStatus()]
  }));

  constructor() {
    // Title and subtitle both update dynamically
    usePageTitle(this.pageTitle);
  }
}
```

#### Example: Search Results with Count

```typescript
@Component({
  selector: 'app-article-search',
  standalone: true,
  template: `...`
})
export class ArticleSearchComponent {
  searchTerm = signal('');
  resultCount = signal(0);

  pageTitle = computed(() => {
    const term = this.searchTerm();
    const count = this.resultCount();
    return {
      title: term ? `Artikelsuche - "${term}"` : 'Artikelsuche',
      subtitle: `${count} Ergebnis${count === 1 ? '' : 'se'}`
    };
  });

  constructor() {
    usePageTitle(this.pageTitle);
  }
}
```

### Optional Title and Subtitle

Both `title` and `subtitle` are optional. When a property is `undefined`, it will not be updated:

#### Skip Title Update (Subtitle Only)

```typescript
// Only update the subtitle, keep existing document title
pageTitle = signal({ subtitle: '3 items' });
usePageTitle(this.pageTitle);
```

#### Skip Subtitle Update (Title Only)

```typescript
// Only update the title, no subtitle
pageTitle = signal({ title: 'Dashboard' });
usePageTitle(this.pageTitle);
```

#### Skip All Updates

```typescript
// Empty object - skips all updates
pageTitle = signal({});
usePageTitle(this.pageTitle);
```

#### Conditional Updates

```typescript
// Skip title update when data is not loaded
pageTitle = computed(() => {
  const name = this.productName();
  return {
    title: name ? `Product - ${name}` : undefined,
    subtitle: 'Loading...'
  };
});
usePageTitle(this.pageTitle);
```

## Migration Guide

### From Resolver-Based Titles

If you're currently using a resolver for titles (e.g., `resolveTitle`), here's how to migrate:

**Before (with resolver):**
```typescript
// In resolver file
export const resolveTitle: (keyOrTitle: string) => ResolveFn<string> =
  (keyOrTitle) => (route, state) => {
    const config = inject(Config);
    const title = inject(Title);
    const tabService = inject(TabService);

    const titleFromConfig = config.get(`process.titles.${keyOrTitle}`, z.string().default(keyOrTitle));
    // ... manual title setting logic
    return titleFromConfig;
  };

// In routing module
{
  path: 'dashboard',
  component: DashboardComponent,
  resolve: { title: resolveTitle('Dashboard') }
}
```

**After (with IsaTitleStrategy):**
```typescript
// No resolver needed - just use route config
{
  path: 'dashboard',
  component: DashboardComponent,
  title: 'Dashboard' // Much simpler!
}
```

**For dynamic titles, use usePageTitle() instead:**
```typescript
// In component
pageTitle = computed(() => ({ title: this.dynamicTitle() }));

constructor() {
  usePageTitle(this.pageTitle);
}
```

## API Reference

### `IsaTitleStrategy`

Custom TitleStrategy implementation that extends Angular's TitleStrategy.

**Methods:**
- `updateTitle(snapshot: RouterStateSnapshot): void` - Called automatically by Angular Router

**Dependencies:**
- `@isa/core/config` - For title prefix configuration
- `@isa/core/tabs` - For tab name updates
- `@angular/platform-browser` - For document title updates

**Configuration:**
```typescript
// In app providers
{ provide: TitleStrategy, useClass: IsaTitleStrategy }
```

### `usePageTitle(titleSubtitleSignal)`

Reactive helper function for managing dynamic component titles and subtitles.

**Parameters:**
- `titleSubtitleSignal: Signal<PageTitleInput>` - A signal containing optional title and subtitle

**Returns:** `void`

**Dependencies:**
- `@isa/core/config` - For title prefix configuration
- `@isa/core/tabs` - For tab name updates
- `@angular/platform-browser` - For document title updates

**Example:**
```typescript
const pageTitle = computed(() => ({
  title: `Search - ${query()}`,
  subtitle: `${count()} results`
}));
usePageTitle(pageTitle);
```

### `PageTitleInput`

Input interface for `usePageTitle()`.

**Properties:**
- `title?: string` - Optional page title (without ISA prefix). When undefined, document title is not updated.
- `subtitle?: string` - Optional subtitle to display in the tab. When undefined, tab subtitle is not updated.

## Best Practices

### ✅ Do

- Use route-based titles for static pages
- Use `usePageTitle()` for dynamic content-dependent titles
- Keep title signals computed from other signals for reactivity
- Use descriptive, user-friendly titles
- Combine route titles with component-level refinements for clarity
- Use subtitles for status indicators, context info, or step numbers
- Return `undefined` for title/subtitle when you want to skip updates

### ❌ Don't

- Add the "ISA" prefix manually (it's added automatically)
- Call `Title.setTitle()` directly (use these utilities instead)
- Create multiple effects updating the same title (use one computed signal)
- Put long, complex logic in title computations (keep them simple)

## Examples from ISA Codebase

### Artikelsuche (Search with Term)
```typescript
@Component({...})
export class ArticleSearchComponent {
  searchTerm = signal('');

  pageTitle = computed(() => {
    const term = this.searchTerm();
    return {
      title: term ? `Artikelsuche - "${term}"` : 'Artikelsuche'
    };
  });

  constructor() {
    usePageTitle(this.pageTitle);
  }
}
```

### Rückgabe Details (Return with ID)
```typescript
@Component({...})
export class ReturnDetailsComponent {
  returnId = signal<string | null>(null);

  pageTitle = computed(() => {
    const id = this.returnId();
    return {
      title: id ? `Rückgabe - ${id}` : 'Rückgabe Details'
    };
  });

  constructor() {
    usePageTitle(this.pageTitle);
  }
}
```

## Testing

Run tests for this library:

```bash
npx nx test common-title-management --skip-nx-cache
```

## Architecture Notes

This library is placed in the **common** domain (not core) because:
- It's a reusable utility pattern that features opt into
- Components can function without it (unlike core infrastructure)
- Provides patterns for solving a recurring problem (page titles)
- Similar to other common libraries like decorators and data-access utilities

## Related Libraries

- `@isa/core/config` - Configuration management
- `@isa/core/tabs` - Multi-tab navigation
- `@isa/core/navigation` - Navigation context preservation

## Support

For issues or questions, refer to the main ISA documentation or contact the development team.
