npm package: @libs-ui/pipes-clone-object
Description: > Version: `0.2.355-15` > > Pipe clone object/array với hỗ trợ shallow clone, deep clone và xử lý Signal tự động.
Latest version: 0.2.356-23

# @libs-ui/pipes-clone-object

> Version: `0.2.355-15`
>
> Pipe clone object/array với hỗ trợ shallow clone, deep clone và xử lý Signal tự động.

## Giới thiệu

`LibsUiPipesCloneObjectPipe` là một Angular pipe đa năng để clone object hoặc array. Pipe hỗ trợ cả shallow clone (nhanh, ít memory) và deep clone (an toàn cho nested objects), đồng thờ tự động unwrap Signal nếu input là Signal.

### Tính năng

- ✅ Shallow clone với spread operator (nhanh)
- ✅ Deep clone với lodash cloneDeep (an toàn nested)
- ✅ Auto-unwrap Signal (không cần gọi `.()`)
- ✅ Default data khi input falsy
- ✅ Hoạt động với cả object và array
- ✅ Standalone pipe (Angular 16+)

## Khi nào sử dụng

### 📝 Form Editing (với Cancel functionality)

Khi bạn cần chỉnh sửa data trong form nhưng muốn có khả năng Cancel để khôi phục data gốc:

```typescript
// ❌ Sai: Gán trực tiếp - cùng reference
this.editUser = this.user;
this.editUser.name = 'Jane'; // ❌ user.name cũng đổi!

// ✅ Đúng: Clone trước khi edit
this.editUser = cloneDeep(this.user);
this.editUser.name = 'Jane'; // ✅ user.name không đổi
// Khi Cancel: đơn giản là bỏ editUser
```

### 🔽 Pass Data xuống Child Component

Khi truyền data xuống component con nhưng không muốn component con làm thay đổi data cha:

```typescript
// Parent Component
items = ['apple', 'banana'];

// ❌ Child có thể mutate parent data
<app-child [items]="items">
// Child: items.push('orange') => Parent.items cũng đổi!

// ✅ Clone trước khi truyền
<app-child [items]="items | LibsUiPipesCloneObjectPipe">
// Child tự do modify mà không ảnh hưởng Parent
```

### ⏪ Undo/Redo Functionality

Lưu trữ các state cũ để khôi phục:

```typescript
history = [cloneDeep(currentState)];

// Sau khi thay đổi
history.push(cloneDeep(newState));

// Undo: khôi phục state trước đó
currentState = cloneDeep(history[history.length - 2]);
```

### 🔄 Signal Unwrap & Clone

Đơn giản hóa template khi làm việc với Signal:

```typescript
// ❌ Không dùng pipe - phải gọi .() nhiều lần
<p>{{ userSignal().name }}</p>
<p>{{ userSignal().email }}</p>

// ✅ Dùng pipe - auto unwrap + clone
@let user = (userSignal | LibsUiPipesCloneObjectPipe);
<p>{{ user.name }}</p>
<p>{{ user.email }}</p>
```

## ⚠️ Important Notes

- **🔄 Signal Auto-Unwrap**: Pipe tự động unwrap Signal nếu input là Signal. Không cần gọi `.()` trong template.
- **🔀 Shallow vs Deep**: Mặc định shallow clone (spread). Dùng `isCloneDeep = true` để deep clone nested objects.
- **📦 Default Data**: Nếu input falsy, trả về `defaultData` hoặc empty object `{}`.

## Cài đặt

```bash
npm install @libs-ui/pipes-clone-object
```

## Import

```typescript
import { LibsUiPipesCloneObjectPipe } from '@libs-ui/pipes-clone-object';

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

## Ví dụ

### 1. Shallow Clone (Mặc định)

```html
@let cloned = (user | LibsUiPipesCloneObjectPipe);

<p>Original: {{ user.name }}</p>
<p>Cloned: {{ cloned.name }}</p>
```

```typescript
user = { name: 'John', age: 30 };
// Shallow clone dùng spread: { ...user }
// Chỉnh sửa clone không ảnh hưởng original (top-level)
```

### 2. Deep Clone

```html
@let cloned = (user | LibsUiPipesCloneObjectPipe : true);

<p>City: {{ cloned.address.city }}</p>
<button (click)="cloned.address.city = 'HN'">Edit</button>
```

```typescript
user = {
  name: 'John',
  address: { city: 'HCM', country: 'VN' }
};

// Deep clone - address là object mới hoàn toàn
// An toàn khi edit nested properties
```

### 3. Clone Array

```html
@let clonedItems = (items | LibsUiPipesCloneObjectPipe);

<ul>
  @for (item of clonedItems; track item) {
    <li>{{ item }}</li>
  }
</ul>
```

```typescript
items = ['apple', 'banana', 'orange'];
// Array clone dùng spread: [...items]
```

### 4. 🔄 Signal Auto-Unwrap

```html
<!-- Không cần gọi userSignal().name -->
@let cloned = (userSignal | LibsUiPipesCloneObjectPipe);
<p>Name: {{ cloned.name }}</p>
```

```typescript
userSignal = signal({ name: 'John', age: 30 });

// Pipe tự động detect Signal và unwrap
```

### 5. Default Data

```html
@let result = (maybeNull | LibsUiPipesCloneObjectPipe : false : { name: 'Default' });
<p>Result: {{ result.name }}</p>
```

```typescript
maybeNull = null;
// Trả về { name: 'Default' } vì input là null
```

## API

### LibsUiPipesCloneObjectPipe

```typescript
data | LibsUiPipesCloneObjectPipe : isCloneDeep? : defaultData?
```

#### Parameters

| Property      | Type                    | Default  | Description                                               |
| ------------- | ----------------------- | -------- | --------------------------------------------------------- |
| `data`        | `any \| Signal<any>`    | -        | Data cần clone (object, array, hoặc Signal).              |
| `isCloneDeep` | `boolean \| undefined`  | `false`  | `true` để deep clone, `false` để shallow clone.           |
| `defaultData` | `any \| undefined`      | `{}`     | Giá trị mặc định trả về nếu data falsy.                   |

#### Returns

`any` - Object/array đã được clone (hoặc defaultData nếu input falsy)

## Hidden Logic

### 1. 🔄 Signal Auto-Unwrapping

Pipe sử dụng `isSignal()` từ @angular/core để detect và auto-unwrap Signal:

```typescript
if (isSignal(data)) {
  data = data(); // Auto-unwrap
}
return isCloneDeep ? cloneDeep(data) : { ...data };
```

**So sánh:**
```typescript
// ❌ Không dùng pipe - phải unwrap thủ công
{{ userSignal().name }}

// ✅ Dùng pipe - auto unwrap
@let cloned = (userSignal | LibsUiPipesCloneObjectPipe);
{{ cloned.name }}
```

### 2. 🔀 Shallow vs Deep Clone

| Loại | Cách làm | Pros | Cons |
| ---- | -------- | ---- | ---- |
| Shallow | `{ ...obj }` hoặc `[ ...arr ]` | Nhanh, ít memory | Nested objects share reference |
| Deep | `cloneDeep(obj)` | An toàn toàn bộ | Chậm hơn, nhiều memory |

```typescript
// Shallow - nested vẫn share reference
const shallow = { ...user };
shallow.name = 'Jane'; // ✅ Không ảnh hưởng original
shallow.address.city = 'HN'; // ❌ Ảnh hưởng original!

// Deep - hoàn toàn independent
const deep = cloneDeep(user);
deep.address.city = 'HN'; // ✅ Không ảnh hưởng original
```

### 3. 📦 Default Data Handling

Khi input là falsy, pipe trả về defaultData hoặc empty object:

```typescript
// Các trường hợp return default
transform(null)           => defaultData || {}
transform(undefined)      => defaultData || {}
transform(0)              => defaultData || {}
transform('')             => defaultData || {}

// Với custom defaultData
transform(null, false, { name: 'Guest' })
// => { name: 'Guest' }
```

## Demo

- **Local Development**: [http://localhost:4500/pipes/clone-object](http://localhost:4500/pipes/clone-object)

## Unit Tests

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

## License

MIT