@libs-ui/pipes-convert-object-to-signal
v0.2.356-25
Published
> 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.
Readme
@libs-ui/pipes-convert-object-to-signal
Version:
0.2.355-15Pipe 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:
// ❌ Nguyên object là Signal - không hiệu quả
user = signal({ name: 'John', email: '[email protected]' });
// user.set({ ...user(), name: 'Jane' }) => Cả name và email đều trigger re-render!
// ✅ Nested Signals - hiệu quả
user = { name: 'John', email: '[email protected]' } | 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:
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:
<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
npm install @libs-ui/pipes-convert-object-to-signalImport
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
@let user = ({ name: 'John', email: '[email protected]', 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>// 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ới2. Array thành Signal của Signals
@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>// items() là WritableSignal<WritableSignal<string>[]>
// items()[0]() - lấy giá trị item đầu tiên
// items()[0].set('new') - set giá trị mới3. Nested Object
@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)
@let user = (userData | LibsUiPipesConvertObjectToSignalPipe : false);
<p>Name: {{ user().name() }}</p>// isCloneDeep = false: Giữ nguyên reference đến data gốc
// Nhanh hơn nhưng cẩn thận mutate!API
LibsUiPipesConvertObjectToSignalPipe
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:
// 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:
// ❌ Object là signal
user = signal({ name: 'John', email: '[email protected]' });
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 |
// 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 |
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
Unit Tests
Xem file test-commands.md để biết cách chạy unit tests.
License
MIT
