npm package: @libs-ui/services-dynamic-component
Description: > Service để tạo và quản lý Angular components động tại runtime.
Latest version: 0.2.356-23

# @libs-ui/services-dynamic-component

> Service để tạo và quản lý Angular components động tại runtime.

## 📦 Cài đặt

```bash
npm install @libs-ui/services-dynamic-component
```

## 🚀 Sử dụng

```typescript
import { LibsUiDynamicComponentService } from '@libs-ui/services-dynamic-component';

@Component({...})
export class MyComponent {
  private dynamicService = inject(LibsUiDynamicComponentService);

  createComponent() {
    const componentRef = this.dynamicService.resolveComponentFactory(MyDynamicComponent);
    this.dynamicService.addToBody(componentRef);
  }
}
```

## 📚 API Reference

### Methods

| Method | Parameters | Returns | Description |
|--------|-----------|---------|-------------|
| `resolveComponentFactory` | `component: any` | `ComponentRef<any>` | Tạo ComponentRef từ component class |
| `addToBody` | `componentRef?: ComponentRef<any>, isAddParentDocument?: boolean, timerDelayUpdateStyleToParent?: number` | `string` | Thêm component vào document.body hoặc parent document |
| `addToElement` | `componentRef: any, elementAdd: any` | `void` | Thêm component vào element cụ thể |
| `addToElementLayoutContentDefault` | `componentRef: any, id?: string` | `void` | Thêm component vào element có id mặc định |
| `addToIdAttributeElement` | `componentRef: any, id: string` | `void` | Thêm component vào element có id cụ thể |
| `delete` | `componentRef: any, options?: { ignoreDestroyComponent?: boolean; timeoutDestroy?: number }` | `void` | Xóa component khỏi DOM và destroy |
| `remove` | `componentRef: any, _?: string, ignoreDestroyComponent?: boolean` | `void` | ⚠️ Deprecated - Dùng `delete()` thay thế |

### Properties

| Property | Type | Description |
|----------|------|-------------|
| `IdElementLayoutContentDefault` | `string` (setter) | Set ID mặc định cho element layout content |

## 🔍 Hidden Logic (Logic Ẩn)

### 1. Singleton Service & State Sharing

Service là singleton (`providedIn: 'root'`), nghĩa là:
- Tất cả instances trong app share cùng một state
- `elementLayoutContent` Map được cache và share giữa tất cả usages
- `elementBody` được cache một lần và reuse

**Ví dụ:**
```typescript
// Component A
this.dynamicService.addToIdAttributeElement(compRef, 'container-1');

// Component B - Cùng service instance
// elementLayoutContent Map đã có 'container-1' được cache
this.dynamicService.addToIdAttributeElement(compRef2, 'container-1');
// Không cần query DOM lại
```

### 2. Element Caching với Map

Service sử dụng `Map<string, HTMLElement>` để cache DOM elements:
- Lần đầu gọi `addToIdAttributeElement()` với id mới: Query DOM và cache
- Lần sau: Lấy từ cache, không query DOM lại
- **Lưu ý**: Nếu element bị xóa khỏi DOM, cache vẫn giữ reference cũ

**Ví dụ:**
```typescript
// Lần 1: Query DOM và cache
this.dynamicService.addToIdAttributeElement(compRef1, 'my-container');
// elementLayoutContent.set('my-container', document.getElementById('my-container'))

// Lần 2: Lấy từ cache (nhanh hơn)
this.dynamicService.addToIdAttributeElement(compRef2, 'my-container');
// elementLayoutContent.get('my-container') - không query DOM
```

### 3. Parent Document Handling (iframe scenarios)

Khi `isAddParentDocument = true`:
- Component được append vào `window.parent.document.body` (không phải current document)
- Styles từ current document được clone và append vào parent document
- WeakMap được dùng để track styles đã copy, tránh duplicate

**Ví dụ:**
```typescript
// Trong iframe
const compRef = this.dynamicService.resolveComponentFactory(MyComponent);
this.dynamicService.addToBody(compRef, true);
// Component xuất hiện trong parent window, không phải iframe
// Styles từ iframe được copy sang parent document
```

### 4. Style Copying Logic

Khi add component vào parent document:
- Tất cả `<style>` tags từ current document được clone
- Cloned styles được append vào parent document head
- WeakMap track ComponentRef → Array<Node> để cleanup sau
- Có debounce delay (mặc định 250ms) để tránh copy nhiều lần

**Ví dụ:**
```typescript
// Current document có 3 style tags
// Khi addToBody(compRef, true):
// - Clone 3 style tags
// - Append vào parent.document.head
// - Track trong mapStylesToParentDocument
```

### 5. ComponentRef Lifecycle Management

Service quản lý lifecycle của ComponentRef:
- `attachView()`: Đăng ký view với ApplicationRef (change detection)
- `detachView()`: Hủy đăng ký view (ngừng change detection)
- `destroy()`: Destroy component hoàn toàn

**Ví dụ:**
```typescript
const compRef = this.dynamicService.resolveComponentFactory(MyComponent);
this.dynamicService.addToBody(compRef);
// attachView() được gọi tự động

// Sau đó
this.dynamicService.delete(compRef);
// detachView() được gọi, sau đó destroy()
```

### 6. Delete Options - ignoreDestroyComponent & timeoutDestroy

`delete()` method có 2 options quan trọng:

**ignoreDestroyComponent = true:**
- Chỉ detach view, không destroy component
- Component vẫn tồn tại trong memory
- Dùng khi muốn reuse component sau

**timeoutDestroy:**
- Delay destroy component sau khi detach view
- Dùng khi cần animation hoặc cleanup async

**Ví dụ:**
```typescript
// Option 1: Không destroy (reuse sau)
this.dynamicService.delete(compRef, { ignoreDestroyComponent: true });
// Component vẫn tồn tại, có thể add lại

// Option 2: Delay destroy (animation)
this.dynamicService.delete(compRef, { timeoutDestroy: 300 });
// detachView() ngay, destroy() sau 300ms
```

### 7. Auto Cleanup trong ngOnDestroy

Service tự động cleanup khi bị destroy:
- Clear timeout nếu có
- Clear elementLayoutContent Map
- **Lưu ý**: Styles đã copy sang parent document KHÔNG được tự động remove

**Ví dụ:**
```typescript
// Service được destroy
ngOnDestroy() {
  // Clear timeout
  // Clear elementLayoutContent Map
  // Nhưng styles trong parent document vẫn còn
}
```

### 8. WeakMap cho Style Tracking

Service dùng WeakMap để track styles:
- Key: ComponentRef (weak reference, không giữ component alive)
- Value: Array<Node> (cloned style tags)
- Khi ComponentRef bị GC, entry tự động bị xóa khỏi WeakMap

**Ví dụ:**
```typescript
const compRef = this.dynamicService.resolveComponentFactory(MyComponent);
this.dynamicService.addToBody(compRef, true);
// mapStylesToParentDocument.set(compRef, [style1, style2, style3])

// Sau khi delete
this.dynamicService.delete(compRef);
// removeStyleToParentDocument() xóa styles khỏi parent document
// WeakMap entry vẫn tồn tại cho đến khi compRef bị GC
```

## ⚠️ Important Notes

- Service là singleton, state được share giữa tất cả usages
- Element caching có thể trở nên stale nếu element bị xóa khỏi DOM
- Styles copy sang parent document cần được cleanup thủ công nếu service bị destroy
- `remove()` method đã deprecated, dùng `delete()` thay thế
- ComponentRef cần được destroy để tránh memory leaks

## 📖 Ví dụ

Xem demo tại: [http://localhost:4500/services/dynamic-component](http://localhost:4500/services/dynamic-component)

## 📝 Version

`0.2.355-10`