@libs-ui/pipes-clone-object 0.2.355-8 → 0.2.356-0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +273 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,3 +1,274 @@
|
|
|
1
|
-
# pipes-clone-object
|
|
1
|
+
# @libs-ui/pipes-clone-object
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> Version: `0.2.355-15`
|
|
4
|
+
>
|
|
5
|
+
> Pipe clone object/array với hỗ trợ shallow clone, deep clone và xử lý Signal tự động.
|
|
6
|
+
|
|
7
|
+
## Giới thiệu
|
|
8
|
+
|
|
9
|
+
`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.
|
|
10
|
+
|
|
11
|
+
### Tính năng
|
|
12
|
+
|
|
13
|
+
- ✅ Shallow clone với spread operator (nhanh)
|
|
14
|
+
- ✅ Deep clone với lodash cloneDeep (an toàn nested)
|
|
15
|
+
- ✅ Auto-unwrap Signal (không cần gọi `.()`)
|
|
16
|
+
- ✅ Default data khi input falsy
|
|
17
|
+
- ✅ Hoạt động với cả object và array
|
|
18
|
+
- ✅ Standalone pipe (Angular 16+)
|
|
19
|
+
|
|
20
|
+
## Khi nào sử dụng
|
|
21
|
+
|
|
22
|
+
### 📝 Form Editing (với Cancel functionality)
|
|
23
|
+
|
|
24
|
+
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:
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
// ❌ Sai: Gán trực tiếp - cùng reference
|
|
28
|
+
this.editUser = this.user;
|
|
29
|
+
this.editUser.name = 'Jane'; // ❌ user.name cũng đổi!
|
|
30
|
+
|
|
31
|
+
// ✅ Đúng: Clone trước khi edit
|
|
32
|
+
this.editUser = cloneDeep(this.user);
|
|
33
|
+
this.editUser.name = 'Jane'; // ✅ user.name không đổi
|
|
34
|
+
// Khi Cancel: đơn giản là bỏ editUser
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### 🔽 Pass Data xuống Child Component
|
|
38
|
+
|
|
39
|
+
Khi truyền data xuống component con nhưng không muốn component con làm thay đổi data cha:
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
// Parent Component
|
|
43
|
+
items = ['apple', 'banana'];
|
|
44
|
+
|
|
45
|
+
// ❌ Child có thể mutate parent data
|
|
46
|
+
<app-child [items]="items">
|
|
47
|
+
// Child: items.push('orange') => Parent.items cũng đổi!
|
|
48
|
+
|
|
49
|
+
// ✅ Clone trước khi truyền
|
|
50
|
+
<app-child [items]="items | LibsUiPipesCloneObjectPipe">
|
|
51
|
+
// Child tự do modify mà không ảnh hưởng Parent
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### ⏪ Undo/Redo Functionality
|
|
55
|
+
|
|
56
|
+
Lưu trữ các state cũ để khôi phục:
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
history = [cloneDeep(currentState)];
|
|
60
|
+
|
|
61
|
+
// Sau khi thay đổi
|
|
62
|
+
history.push(cloneDeep(newState));
|
|
63
|
+
|
|
64
|
+
// Undo: khôi phục state trước đó
|
|
65
|
+
currentState = cloneDeep(history[history.length - 2]);
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### 🔄 Signal Unwrap & Clone
|
|
69
|
+
|
|
70
|
+
Đơn giản hóa template khi làm việc với Signal:
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
// ❌ Không dùng pipe - phải gọi .() nhiều lần
|
|
74
|
+
<p>{{ userSignal().name }}</p>
|
|
75
|
+
<p>{{ userSignal().email }}</p>
|
|
76
|
+
|
|
77
|
+
// ✅ Dùng pipe - auto unwrap + clone
|
|
78
|
+
@let user = (userSignal | LibsUiPipesCloneObjectPipe);
|
|
79
|
+
<p>{{ user.name }}</p>
|
|
80
|
+
<p>{{ user.email }}</p>
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## ⚠️ Important Notes
|
|
84
|
+
|
|
85
|
+
- **🔄 Signal Auto-Unwrap**: Pipe tự động unwrap Signal nếu input là Signal. Không cần gọi `.()` trong template.
|
|
86
|
+
- **🔀 Shallow vs Deep**: Mặc định shallow clone (spread). Dùng `isCloneDeep = true` để deep clone nested objects.
|
|
87
|
+
- **📦 Default Data**: Nếu input falsy, trả về `defaultData` hoặc empty object `{}`.
|
|
88
|
+
|
|
89
|
+
## Cài đặt
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
npm install @libs-ui/pipes-clone-object
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Import
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
import { LibsUiPipesCloneObjectPipe } from '@libs-ui/pipes-clone-object';
|
|
99
|
+
|
|
100
|
+
@Component({
|
|
101
|
+
standalone: true,
|
|
102
|
+
imports: [LibsUiPipesCloneObjectPipe],
|
|
103
|
+
// ...
|
|
104
|
+
})
|
|
105
|
+
export class YourComponent {}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Ví dụ
|
|
109
|
+
|
|
110
|
+
### 1. Shallow Clone (Mặc định)
|
|
111
|
+
|
|
112
|
+
```html
|
|
113
|
+
@let cloned = (user | LibsUiPipesCloneObjectPipe);
|
|
114
|
+
|
|
115
|
+
<p>Original: {{ user.name }}</p>
|
|
116
|
+
<p>Cloned: {{ cloned.name }}</p>
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
user = { name: 'John', age: 30 };
|
|
121
|
+
// Shallow clone dùng spread: { ...user }
|
|
122
|
+
// Chỉnh sửa clone không ảnh hưởng original (top-level)
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### 2. Deep Clone
|
|
126
|
+
|
|
127
|
+
```html
|
|
128
|
+
@let cloned = (user | LibsUiPipesCloneObjectPipe : true);
|
|
129
|
+
|
|
130
|
+
<p>City: {{ cloned.address.city }}</p>
|
|
131
|
+
<button (click)="cloned.address.city = 'HN'">Edit</button>
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
user = {
|
|
136
|
+
name: 'John',
|
|
137
|
+
address: { city: 'HCM', country: 'VN' }
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
// Deep clone - address là object mới hoàn toàn
|
|
141
|
+
// An toàn khi edit nested properties
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### 3. Clone Array
|
|
145
|
+
|
|
146
|
+
```html
|
|
147
|
+
@let clonedItems = (items | LibsUiPipesCloneObjectPipe);
|
|
148
|
+
|
|
149
|
+
<ul>
|
|
150
|
+
@for (item of clonedItems; track item) {
|
|
151
|
+
<li>{{ item }}</li>
|
|
152
|
+
}
|
|
153
|
+
</ul>
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
items = ['apple', 'banana', 'orange'];
|
|
158
|
+
// Array clone dùng spread: [...items]
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### 4. 🔄 Signal Auto-Unwrap
|
|
162
|
+
|
|
163
|
+
```html
|
|
164
|
+
<!-- Không cần gọi userSignal().name -->
|
|
165
|
+
@let cloned = (userSignal | LibsUiPipesCloneObjectPipe);
|
|
166
|
+
<p>Name: {{ cloned.name }}</p>
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
```typescript
|
|
170
|
+
userSignal = signal({ name: 'John', age: 30 });
|
|
171
|
+
|
|
172
|
+
// Pipe tự động detect Signal và unwrap
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### 5. Default Data
|
|
176
|
+
|
|
177
|
+
```html
|
|
178
|
+
@let result = (maybeNull | LibsUiPipesCloneObjectPipe : false : { name: 'Default' });
|
|
179
|
+
<p>Result: {{ result.name }}</p>
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
```typescript
|
|
183
|
+
maybeNull = null;
|
|
184
|
+
// Trả về { name: 'Default' } vì input là null
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## API
|
|
188
|
+
|
|
189
|
+
### LibsUiPipesCloneObjectPipe
|
|
190
|
+
|
|
191
|
+
```typescript
|
|
192
|
+
data | LibsUiPipesCloneObjectPipe : isCloneDeep? : defaultData?
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
#### Parameters
|
|
196
|
+
|
|
197
|
+
| Property | Type | Default | Description |
|
|
198
|
+
| ------------- | ----------------------- | -------- | --------------------------------------------------------- |
|
|
199
|
+
| `data` | `any \| Signal<any>` | - | Data cần clone (object, array, hoặc Signal). |
|
|
200
|
+
| `isCloneDeep` | `boolean \| undefined` | `false` | `true` để deep clone, `false` để shallow clone. |
|
|
201
|
+
| `defaultData` | `any \| undefined` | `{}` | Giá trị mặc định trả về nếu data falsy. |
|
|
202
|
+
|
|
203
|
+
#### Returns
|
|
204
|
+
|
|
205
|
+
`any` - Object/array đã được clone (hoặc defaultData nếu input falsy)
|
|
206
|
+
|
|
207
|
+
## Hidden Logic
|
|
208
|
+
|
|
209
|
+
### 1. 🔄 Signal Auto-Unwrapping
|
|
210
|
+
|
|
211
|
+
Pipe sử dụng `isSignal()` từ @angular/core để detect và auto-unwrap Signal:
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
if (isSignal(data)) {
|
|
215
|
+
data = data(); // Auto-unwrap
|
|
216
|
+
}
|
|
217
|
+
return isCloneDeep ? cloneDeep(data) : { ...data };
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
**So sánh:**
|
|
221
|
+
```typescript
|
|
222
|
+
// ❌ Không dùng pipe - phải unwrap thủ công
|
|
223
|
+
{{ userSignal().name }}
|
|
224
|
+
|
|
225
|
+
// ✅ Dùng pipe - auto unwrap
|
|
226
|
+
@let cloned = (userSignal | LibsUiPipesCloneObjectPipe);
|
|
227
|
+
{{ cloned.name }}
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### 2. 🔀 Shallow vs Deep Clone
|
|
231
|
+
|
|
232
|
+
| Loại | Cách làm | Pros | Cons |
|
|
233
|
+
| ---- | -------- | ---- | ---- |
|
|
234
|
+
| Shallow | `{ ...obj }` hoặc `[ ...arr ]` | Nhanh, ít memory | Nested objects share reference |
|
|
235
|
+
| Deep | `cloneDeep(obj)` | An toàn toàn bộ | Chậm hơn, nhiều memory |
|
|
236
|
+
|
|
237
|
+
```typescript
|
|
238
|
+
// Shallow - nested vẫn share reference
|
|
239
|
+
const shallow = { ...user };
|
|
240
|
+
shallow.name = 'Jane'; // ✅ Không ảnh hưởng original
|
|
241
|
+
shallow.address.city = 'HN'; // ❌ Ảnh hưởng original!
|
|
242
|
+
|
|
243
|
+
// Deep - hoàn toàn independent
|
|
244
|
+
const deep = cloneDeep(user);
|
|
245
|
+
deep.address.city = 'HN'; // ✅ Không ảnh hưởng original
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### 3. 📦 Default Data Handling
|
|
249
|
+
|
|
250
|
+
Khi input là falsy, pipe trả về defaultData hoặc empty object:
|
|
251
|
+
|
|
252
|
+
```typescript
|
|
253
|
+
// Các trường hợp return default
|
|
254
|
+
transform(null) => defaultData || {}
|
|
255
|
+
transform(undefined) => defaultData || {}
|
|
256
|
+
transform(0) => defaultData || {}
|
|
257
|
+
transform('') => defaultData || {}
|
|
258
|
+
|
|
259
|
+
// Với custom defaultData
|
|
260
|
+
transform(null, false, { name: 'Guest' })
|
|
261
|
+
// => { name: 'Guest' }
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
## Demo
|
|
265
|
+
|
|
266
|
+
- **Local Development**: [http://localhost:4500/pipes/clone-object](http://localhost:4500/pipes/clone-object)
|
|
267
|
+
|
|
268
|
+
## Unit Tests
|
|
269
|
+
|
|
270
|
+
Xem file `test-commands.md` để biết cách chạy unit tests.
|
|
271
|
+
|
|
272
|
+
## License
|
|
273
|
+
|
|
274
|
+
MIT
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@libs-ui/pipes-clone-object",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.356-0",
|
|
4
4
|
"peerDependencies": {
|
|
5
5
|
"@angular/core": ">=18.0.0",
|
|
6
|
-
"@libs-ui/utils": "0.2.
|
|
6
|
+
"@libs-ui/utils": "0.2.356-0"
|
|
7
7
|
},
|
|
8
8
|
"sideEffects": false,
|
|
9
9
|
"module": "fesm2022/libs-ui-pipes-clone-object.mjs",
|