@libs-ui/pipes-clone-object
v0.2.357-2
Published
> Angular pipe clone object/array với hỗ trợ shallow clone, deep clone và tự động unwrap Signal.
Readme
@libs-ui/pipes-clone-object
Angular pipe clone object/array với hỗ trợ shallow clone, deep clone và tự động unwrap Signal.
Giới thiệu
LibsUiPipesCloneObjectPipe là một Angular standalone pipe đa năng dùng để clone object hoặc array trực tiếp trong template. Pipe hỗ trợ cả shallow clone (nhanh, ít memory) và deep clone (an toàn cho nested objects), đồng thời tự động phát hiện và unwrap Angular Signal nếu input là Signal — không cần gọi () trong template. Khi input là falsy, pipe trả về defaultData hoặc object rỗng {} để tránh lỗi undefined.
Tính năng
- ✅ Shallow clone với spread operator (
{ ...obj }/[ ...arr ]) — nhanh, ít memory - ✅ Deep clone với
cloneDeeptừ@libs-ui/utils— an toàn cho nested objects - ✅ Tự động unwrap Signal — không cần gọi
.()trong template - ✅ Default data khi input là falsy (
null,undefined,0,"",false) - ✅ Hoạt động với cả object và array
- ✅ Standalone pipe (Angular 16+), không cần NgModule
Khi nào sử dụng
- Form Editing với Cancel: Clone data trước khi cho user chỉnh sửa. Khi Cancel, data gốc không bị ảnh hưởng vì form đang thao tác trên bản clone.
- Truyền data xuống Child Component: Clone trước khi truyền để component con tự do modify mà không làm thay đổi data ở component cha.
- Undo/Redo Functionality: Lưu trữ các snapshot state cũ để có thể khôi phục.
- Signal Unwrap trong template: Dùng pipe để tự động unwrap Signal và clone giá trị, giúp template gọn hơn khi cần truy cập nhiều property.
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 {}Transform
value | LibsUiPipesCloneObjectPipe : isCloneDeep? : defaultData?| Tham số | Type | Bắt buộc | Default | Mô tả | Ví dụ |
|---|---|---|---|---|---|
| data | any \| Signal<any> | Có | — | Object, array hoặc Signal cần clone | user \| LibsUiPipesCloneObjectPipe |
| isCloneDeep | boolean | Không | false | true để deep clone toàn bộ nested structure; false để shallow clone | user \| LibsUiPipesCloneObjectPipe : true |
| defaultData | any | Không | {} | Giá trị trả về khi data là falsy | val \| LibsUiPipesCloneObjectPipe : false : { name: 'Guest' } |
Return: any — object/array đã được clone, hoặc defaultData nếu input là falsy.
Ví dụ sử dụng
1. Shallow Clone (mặc định)
Phù hợp khi object chỉ có 1 cấp (không nested) hoặc cần hiệu năng cao.
// component.ts
import { LibsUiPipesCloneObjectPipe } from '@libs-ui/pipes-clone-object';
@Component({
standalone: true,
imports: [LibsUiPipesCloneObjectPipe],
template: `
@let editUser = (user | LibsUiPipesCloneObjectPipe);
<p>Original: {{ user.name }}</p>
<p>Clone: {{ editUser.name }}</p>
`,
})
export class UserFormComponent {
user = { name: 'John', email: '[email protected]' };
}Shallow clone dùng spread { ...user }. Thay đổi top-level property trên clone sẽ không ảnh hưởng object gốc. Tuy nhiên, nếu object có nested property (như user.address), hai bên vẫn dùng chung reference.
2. Deep Clone cho Nested Objects
Dùng khi object có nested structure và cần chỉnh sửa an toàn ở bất kỳ cấp nào.
// component.ts
import { LibsUiPipesCloneObjectPipe } from '@libs-ui/pipes-clone-object';
@Component({
standalone: true,
imports: [LibsUiPipesCloneObjectPipe],
template: `
@let cloned = (company | LibsUiPipesCloneObjectPipe : true);
<p>CEO gốc: {{ company.ceo.name }}</p>
<p>CEO clone: {{ cloned.ceo.name }}</p>
`,
})
export class CompanyFormComponent {
company = {
name: 'Tech Corp',
ceo: { name: 'John', address: { city: 'HCM' } },
};
}Truyền true làm tham số thứ hai để kích hoạt deep clone. Mọi cấp nested đều là object mới hoàn toàn — chỉnh sửa clone không ảnh hưởng gì đến original.
3. Clone Array
// component.ts
import { LibsUiPipesCloneObjectPipe } from '@libs-ui/pipes-clone-object';
@Component({
standalone: true,
imports: [LibsUiPipesCloneObjectPipe],
template: `
@let clonedItems = (items | LibsUiPipesCloneObjectPipe);
<ul>
@for (item of clonedItems; track item) {
<li>{{ item }}</li>
}
</ul>
`,
})
export class ListComponent {
items = ['apple', 'banana', 'orange'];
}Pipe tự nhận diện array và dùng spread [ ...arr ] để clone. Push/splice trên clonedItems sẽ không thay đổi mảng items gốc.
4. Signal Auto-Unwrap
Pipe tự động phát hiện Angular Signal bằng isSignal() và gọi data() trước khi clone — không cần làm thủ công trong template.
// component.ts
import { Component, signal } from '@angular/core';
import { LibsUiPipesCloneObjectPipe } from '@libs-ui/pipes-clone-object';
@Component({
standalone: true,
imports: [LibsUiPipesCloneObjectPipe],
template: `
@let user = (userSignal | LibsUiPipesCloneObjectPipe);
<p>Name: {{ user.name }}</p>
<p>Age: {{ user.age }}</p>
`,
})
export class ProfileComponent {
userSignal = signal({ name: 'John', age: 30 });
}Không cần viết userSignal() trong template — pipe tự unwrap và clone.
5. Default Data cho Falsy Input
Dùng khi data có thể là null hoặc undefined, để tránh lỗi Cannot read property of null trong template.
// component.ts
import { LibsUiPipesCloneObjectPipe } from '@libs-ui/pipes-clone-object';
@Component({
standalone: true,
imports: [LibsUiPipesCloneObjectPipe],
template: `
@let config = (apiConfig | LibsUiPipesCloneObjectPipe : false : defaultConfig);
<p>API URL: {{ config.apiUrl }}</p>
<p>Timeout: {{ config.timeout }}ms</p>
`,
})
export class SettingsComponent {
apiConfig: { apiUrl: string; timeout: number } | null = null;
defaultConfig = { apiUrl: '/api/v1', timeout: 5000 };
}Khi apiConfig là null, pipe trả về clone của defaultConfig. Truyền false làm isCloneDeep (tham số thứ 2) để không nhầm với defaultData.
6. Dùng trong TypeScript (không cần inject pipe)
Pipe là thin wrapper của cloneDeep từ @libs-ui/utils. Khi cần clone trong file .ts, import thẳng hàm đó — không cần inject pipe.
import { cloneDeep } from '@libs-ui/utils';
// Shallow clone
const shallow = { ...this.user };
shallow.name = 'Jane'; // user.name không thay đổi (top-level)
// Deep clone
const deep = cloneDeep(this.user);
deep.address.city = 'HN'; // user.address.city không thay đổiLogic ẩn quan trọng
Signal Auto-Unwrapping
// Bên trong pipe
if (isSignal(data)) {
data = data(); // tự động unwrap Signal
}
return isCloneDeep ? cloneDeep(data) : Array.isArray(data) ? [...data] : { ...data };Pipe dùng isSignal() từ @angular/core để detect Signal tại runtime. Điều này hoạt động với cả signal(), computed() và input().
Shallow vs Deep — khi nào chọn cái nào
| Tiêu chí | Shallow (false) | Deep (true) |
|---|---|---|
| Cách thực hiện | { ...obj } hoặc [ ...arr ] | cloneDeep(obj) từ lodash |
| Tốc độ | Nhanh | Chậm hơn |
| Memory | Ít | Nhiều hơn |
| An toàn nested | Không — nested share reference | Có — toàn bộ cây object mới |
| Nên dùng khi | Object phẳng, 1 cấp | Object có nested structure |
Default data handling
Pipe kiểm tra input với if (!data) — tất cả các giá trị falsy đều kích hoạt fallback:
transform(null) => defaultData ?? {}
transform(undefined) => defaultData ?? {}
transform(0) => defaultData ?? {}
transform('') => defaultData ?? {}
transform(false) => defaultData ?? {}Lưu ý quan trọng
⚠️ Shallow clone không bảo vệ nested objects: Khi dùng shallow clone (mặc định), chỉ các property ở cấp đầu tiên là độc lập. Nested object vẫn dùng chung reference với original. Nếu cần chỉnh sửa property lồng sâu, bắt buộc dùng isCloneDeep = true.
⚠️ Deep clone tốn tài nguyên hơn: cloneDeep từ lodash clone toàn bộ cây object. Tránh dùng trong vòng lặp lớn hoặc với object cực kỳ lớn. Với list, cân nhắc chỉ clone khi thực sự cần thay đổi nested data.
⚠️ Signal unwrap xảy ra trước khi clone: Nếu input là Signal<Signal<T>> (signal lồng signal), chỉ cấp ngoài cùng được unwrap. Pipe không đệ quy unwrap nhiều cấp signal.
⚠️ Không dùng @libs-ui/exports: BẮT BUỘC import từ entrypoint cụ thể @libs-ui/pipes-clone-object, không import từ @libs-ui/exports.
Demo
npx nx serve core-uiTruy cập: http://localhost:4500/pipes/clone-object
