@libs-ui/pipes-clone-object
v0.2.356-1
Published
> Version: `0.2.355-15` > > Pipe clone object/array với hỗ trợ shallow clone, deep clone và xử lý Signal tự động.
Readme
@libs-ui/pipes-clone-object
Version:
0.2.355-15Pipe 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:
// ❌ 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:
// 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:
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:
// ❌ 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ề
defaultDatahoặc empty object{}.
Cài đặt
npm install @libs-ui/pipes-clone-objectImport
import { LibsUiPipesCloneObjectPipe } from '@libs-ui/pipes-clone-object';
@Component({
standalone: true,
imports: [LibsUiPipesCloneObjectPipe],
// ...
})
export class YourComponent {}Ví dụ
1. Shallow Clone (Mặc định)
@let cloned = (user | LibsUiPipesCloneObjectPipe);
<p>Original: {{ user.name }}</p>
<p>Cloned: {{ cloned.name }}</p>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
@let cloned = (user | LibsUiPipesCloneObjectPipe : true);
<p>City: {{ cloned.address.city }}</p>
<button (click)="cloned.address.city = 'HN'">Edit</button>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 properties3. Clone Array
@let clonedItems = (items | LibsUiPipesCloneObjectPipe);
<ul>
@for (item of clonedItems; track item) {
<li>{{ item }}</li>
}
</ul>items = ['apple', 'banana', 'orange'];
// Array clone dùng spread: [...items]4. 🔄 Signal Auto-Unwrap
<!-- Không cần gọi userSignal().name -->
@let cloned = (userSignal | LibsUiPipesCloneObjectPipe);
<p>Name: {{ cloned.name }}</p>userSignal = signal({ name: 'John', age: 30 });
// Pipe tự động detect Signal và unwrap5. Default Data
@let result = (maybeNull | LibsUiPipesCloneObjectPipe : false : { name: 'Default' });
<p>Result: {{ result.name }}</p>maybeNull = null;
// Trả về { name: 'Default' } vì input là nullAPI
LibsUiPipesCloneObjectPipe
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:
if (isSignal(data)) {
data = data(); // Auto-unwrap
}
return isCloneDeep ? cloneDeep(data) : { ...data };So sánh:
// ❌ 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 |
// 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 original3. 📦 Default Data Handling
Khi input là falsy, pipe trả về defaultData hoặc empty object:
// 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
Unit Tests
Xem file test-commands.md để biết cách chạy unit tests.
License
MIT
