npm package: @libs-ui/pipes-convert-object-to-signal
Description: > Version: `0.2.355-15` > > Pipe chuyển đổi object/array thành cấu trúc Signals lồng nhau để tận dụng fine-grained reactivity.
Latest version: 0.2.356-23

# @libs-ui/pipes-convert-object-to-signal

> Version: `0.2.355-15`
>
> Pipe chuyển đổi object/array thành cấu trúc Signals lồng nhau để tận dụng fine-grained reactivity.

## Giới thiệu

`LibsUiPipesConvertObjectToSignalPipe` chuyển đổi một object hoặc array thành cấu trúc Signals lồng nhau (nested signals). Mỗi property của object trở thành một Signal riêng biệt, cho phép Angular chỉ re-render các phần UI thực sự thay đổi thay vì cả component.

### Tính năng

- ✅ Chuyển object thành nested WritableSignals
- ✅ Chuyển array thành Signal chứa các Signal items
- ✅ Deep clone mặc định (an toàn)
- ✅ Hỗ trợ nested objects (multi-level)
- ✅ Fine-grained reactivity - tối ưu performance

## Khi nào sử dụng

### 📝 Form với nhiều fields

Khi form có nhiều field, dùng nested signals để thay đổi một field không ảnh hưởng đến các field khác:

```typescript
// ❌ Nguyên object là Signal - không hiệu quả
user = signal({ name: 'John', email: 'john@example.com' });
// user.set({ ...user(), name: 'Jane' }) => Cả name và email đều trigger re-render!

// ✅ Nested Signals - hiệu quả
user = { name: 'John', email: 'john@example.com' } | LibsUiPipesConvertObjectToSignalPipe;
user().name.set('Jane'); // => Chỉ name trigger re-render
```

### 📊 Data table lớn

Trong bảng dữ liệu lớn, chỉ re-render cell thay đổi thay vì cả row:

```typescript
rows = data | LibsUiPipesConvertObjectToSignalPipe;

// Trong template
@for (row of rows(); track row().id) {
  <tr>
    <td>{{ row().name() }}</td> <!-- Chỉ re-render khi name thay đổi -->
    <td>{{ row().email() }}</td> <!-- Không re-render khi name thay đổi -->
  </tr>
}
```

### 🔄 Two-way binding tối ưu

Dễ dàng bind từng property đến component con:

```typescript
<app-input-field 
  [value]="user().name()"
  (change)="user().name.set($event)"
/>
<app-input-field 
  [value]="user().email()"
  (change)="user().email.set($event)"
/>
```

## ⚠️ Important Notes

- **🔄 Nested Signals**: Mỗi property của object trở thành một Signal. Thay đổi một property chỉ trigger re-render ở component sử dụng property đó.
- **🔀 Shallow vs Deep Clone**: Mặc định deep clone để tránh mutate data gốc. Dùng `isCloneDeep = false` để giữ nguyên reference.
- **⚡ Fine-grained Reactivity**: Hiệu quả hơn so với nguyên object làm Signal vì chỉ re-render khi property cụ thể thay đổi.

## Cài đặt

```bash
npm install @libs-ui/pipes-convert-object-to-signal
```

## Import

```typescript
import { LibsUiPipesConvertObjectToSignalPipe } from '@libs-ui/pipes-convert-object-to-signal';

@Component({
  standalone: true,
  imports: [LibsUiPipesConvertObjectToSignalPipe],
  // ...
})
export class YourComponent {}
```

## Ví dụ

### 1. Object thành Nested Signals

```html
@let user = ({ name: 'John', email: 'john@example.com', age: 30 } | LibsUiPipesConvertObjectToSignalPipe);

<div>
  <input [value]="user().name()" (input)="user().name.set($event.target.value)" />
  <input [value]="user().email()" (input)="user().email.set($event.target.value)" />
</div>

<p>Name: {{ user().name() }}</p>
<p>Email: {{ user().email() }}</p>
```

```typescript
// user() trả về object với mỗi property là WritableSignal
// user().name() - lấy giá trị
// user().name.set('Jane') - set giá trị mới
```

### 2. Array thành Signal của Signals

```html
@let items = (['apple', 'banana', 'orange'] | LibsUiPipesConvertObjectToSignalPipe);

<ul>
  @for (item of items(); track $index) {
    <li>
      <input [value]="item()" (input)="item.set($event.target.value)" />
    </li>
  }
</ul>
```

```typescript
// items() là WritableSignal<WritableSignal<string>[]>
// items()[0]() - lấy giá trị item đầu tiên
// items()[0].set('new') - set giá trị mới
```

### 3. Nested Object

```html
@let company = ({
  name: 'Tech Corp',
  ceo: { name: 'John', age: 45 }
} | LibsUiPipesConvertObjectToSignalPipe);

<input [value]="company().name()" (input)="company().name.set($event.target.value)" />
<input [value]="company().ceo().name()" (input)="company().ceo().name.set($event.target.value)" />

<p>Company: {{ company().name() }}</p>
<p>CEO: {{ company().ceo().name() }}</p>
```

### 4. Giữ nguyên Reference (nhanh hơn)

```html
@let user = (userData | LibsUiPipesConvertObjectToSignalPipe : false);

<p>Name: {{ user().name() }}</p>
```

```typescript
// isCloneDeep = false: Giữ nguyên reference đến data gốc
// Nhanh hơn nhưng cẩn thận mutate!
```

## API

### LibsUiPipesConvertObjectToSignalPipe

```typescript
data | LibsUiPipesConvertObjectToSignalPipe : isCloneDeep?
```

#### Parameters

| Property      | Type                    | Default  | Description                                               |
| ------------- | ----------------------- | -------- | --------------------------------------------------------- |
| `data`        | `any`                   | -        | Object/array cần chuyển đổi thành Signals.                |
| `isCloneDeep` | `boolean \| undefined`  | `true`   | `true` để deep clone (an toàn), `false` để giữ reference. |

#### Returns

Nested structure với WritableSignals:

```typescript
// Object -> WritableSignal<{ prop: WritableSignal<...> }>
// Array -> WritableSignal<WritableSignal<T>[]>
```

## Hidden Logic

### 1. 🔄 Fine-grained Reactivity

Khác với nguyên object làm Signal, nested signals chỉ re-render component khi property cụ thể thay đổi:

```typescript
// ❌ Object là signal
user = signal({ name: 'John', email: 'john@example.com' });
user.set({ ...user(), name: 'Jane' });
// => Cả name và email đều trigger re-render!

// ✅ Nested signals
user = object | LibsUiPipesConvertObjectToSignalPipe;
user().name.set('Jane');
// => Chỉ name trigger re-render, email không!
```

### 2. 🔀 Shallow vs Deep Clone

| Tùy chọn | Behavior | Khi nào dùng |
| -------- | -------- | ------------ |
| `true` (default) | Deep clone data trước khi convert | Luôn an toàn, không mutate gốc |
| `false` | Giữ nguyên reference | Performance critical, chắc chắn không mutate |

```typescript
// Deep clone (default) - An toàn
user = userData | LibsUiPipesConvertObjectToSignalPipe;
user().name.set('Jane'); // Không ảnh hưởng userData

// No clone - Nhanh hơn
user = userData | LibsUiPipesConvertObjectToSignalPipe : false;
user().name.set('Jane'); // Cẩn thận: ảnh hưởng userData!
```

### 3. 📦 Các kiểu dữ liệu được hỗ trợ

| Kiểu dữ liệu | Kết quả |
| ------------ | ------- |
| Primitives (string, number, boolean) | Trả về nguyên (không convert) |
| Object | WritableSignal với mỗi property là Signal |
| Array | WritableSignal chứa array của Signals |
| Map/Set | Được xử lý đặc biệt |
| Promise/Observer | Trả về nguyên (async objects) |
| null/undefined | Trả về nguyên |

```typescript
123 | pipe => 123 (primitive)
{ name: 'John' } | pipe => WritableSignal<...> (object)
['a', 'b'] | pipe => WritableSignal<WritableSignal<string>[]> (array)
Promise/Observer | pipe => Trả về nguyên (async)
```

## Demo

- **Local Development**: [http://localhost:4500/pipes/convert-object-to-signal](http://localhost:4500/pipes/convert-object-to-signal)

## Unit Tests

Xem file `test-commands.md` để biết cách chạy unit tests.

## License

MIT