npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@libs-ui/pipes-convert-object-to-signal

v0.2.357-7

Published

> Pipe chuyển đổi object/array thành cấu trúc Signals lồng nhau để tận dụng fine-grained reactivity của Angular.

Readme

@libs-ui/pipes-convert-object-to-signal

Pipe chuyển đổi object/array thành cấu trúc Signals lồng nhau để tận dụng fine-grained reactivity của Angular.

Giới thiệu

LibsUiPipesConvertObjectToSignalPipe chuyển đổi một plain object hoặc array thành cấu trúc nested WritableSignal. Mỗi property của object trở thành một WritableSignal riêng biệt, cho phép Angular chỉ re-render đúng phần UI phụ thuộc vào property đó thay vì toàn bộ component. Pipe ủy quyền xử lý cho hàm tiện ích convertObjectToSignal từ @libs-ui/utils.

Tính năng

  • ✅ Chuyển object thành nested WritableSignal — mỗi property là một signal độc lập
  • ✅ Chuyển array thành WritableSignal<WritableSignal<T>[]> — mỗi phần tử là một signal
  • ✅ Hỗ trợ nested objects đa cấp (multi-level)
  • ✅ Deep clone mặc định — an toàn, không mutate data gốc
  • ✅ Tùy chọn shallow (no-clone) để tăng performance khi cần thiết
  • ✅ Fine-grained reactivity — tối ưu hiệu năng cho form/table phức tạp
  • ✅ Trả về nguyên nếu input là null hoặc undefined

Khi nào sử dụng

  • Form nhiều fields: Thay đổi một field không trigger re-render các field khác — mỗi <input> bind vào signal riêng.
  • Data table lớn: Chỉ re-render cell thay đổi thay vì cả row khi cập nhật một giá trị.
  • Two-way binding tối ưu: Bind từng property đến component con mà không cần spread cả object.
  • Performance-critical UI: Giảm tối đa số lần re-render trong component phức tạp có nhiều phần tử phụ thuộc cùng một nguồn dữ liệu.

Cài đặt

npm install @libs-ui/pipes-convert-object-to-signal

Import

import { LibsUiPipesConvertObjectToSignalPipe } from '@libs-ui/pipes-convert-object-to-signal';

@Component({
  standalone: true,
  imports: [LibsUiPipesConvertObjectToSignalPipe],
  // ...
})
export class MyComponent {}

Khi cần dùng trong class TypeScript (inject trực tiếp):

import { LibsUiPipesConvertObjectToSignalPipe } from '@libs-ui/pipes-convert-object-to-signal';

@Component({
  standalone: true,
  imports: [LibsUiPipesConvertObjectToSignalPipe],
  providers: [LibsUiPipesConvertObjectToSignalPipe],
})
export class MyComponent {
  private readonly convertPipe = inject(LibsUiPipesConvertObjectToSignalPipe);

  constructor() {
    this.userSig = this.convertPipe.transform({ name: 'John', age: 30 });
  }
}

Ví dụ sử dụng

1. Object thành Nested Signals (Template)

@let user = ({ name: 'John', email: '[email protected]', age: 30 } | LibsUiPipesConvertObjectToSignalPipe);

<input [value]="user().name()" (input)="user().name.set($any($event.target).value)" />
<input [value]="user().email()" (input)="user().email.set($any($event.target).value)" />
<input type="number" [value]="user().age()" (input)="user().age.set(+$any($event.target).value)" />

<p>Name: {{ user().name() }}</p>
<p>Email: {{ user().email() }}</p>
<p>Age: {{ user().age() }}</p>
// Đọc signal: user().name()
// Set signal: user().name.set('Jane')
// Thay đổi name KHÔNG trigger re-render cho email và age

2. Array thành Signal của Signals

@let items = (['apple', 'banana', 'orange'] | LibsUiPipesConvertObjectToSignalPipe);

<ul>
  @for (item of items(); track item) {
    <li>
      <input [value]="item()" (input)="item.set($any($event.target).value)" />
      <span>{{ item() }}</span>
    </li>
  }
</ul>
// items() → WritableSignal<WritableSignal<string>[]>
// items()[0]() → 'apple'
// items()[0].set('grape') → chỉ re-render item[0]

3. Nested Object (Multi-level)

@let company = ({
  name: 'Tech Corp',
  ceo: { name: 'John', age: 45 }
} | LibsUiPipesConvertObjectToSignalPipe);

<input [value]="company().name()" (input)="company().name.set($any($event.target).value)" />
<input [value]="company().ceo().name()" (input)="company().ceo().name.set($any($event.target).value)" />
<input type="number" [value]="company().ceo().age()" (input)="company().ceo().age.set(+$any($event.target).value)" />

<p>Company: {{ company().name() }}</p>
<p>CEO: {{ company().ceo().name() }}, tuổi {{ company().ceo().age() }}</p>

4. Shallow (No-clone) — giữ nguyên reference

@let user = (userData | LibsUiPipesConvertObjectToSignalPipe : false);

<p>Name: {{ user().name() }}</p>
readonly userData = { name: 'John', age: 30 };

// isCloneDeep = false: nhanh hơn, nhưng mutate userData cũng ảnh hưởng signal
// isCloneDeep = true (default): an toàn, data được deep clone trước khi convert

5. Sử dụng trong TypeScript (inject pipe)

import { LibsUiPipesConvertObjectToSignalPipe } from '@libs-ui/pipes-convert-object-to-signal';

@Component({
  standalone: true,
  imports: [LibsUiPipesConvertObjectToSignalPipe],
  providers: [LibsUiPipesConvertObjectToSignalPipe],
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class UserFormComponent {
  private readonly convertPipe = inject(LibsUiPipesConvertObjectToSignalPipe);

  protected userSig = this.convertPipe.transform({ name: 'John', email: '[email protected]' });

  protected handlerSaveName(event: Event): void {
    event.stopPropagation();
    const value = (event.target as HTMLInputElement).value;
    this.userSig().name.set(value);
  }
}

Transform

| Tham số | Type | Bắt buộc | Default | Mô tả | Ví dụ | |---|---|---|---|---|---| | data | any | Có | — | Object hoặc array cần chuyển đổi thành nested Signals. Trả về nguyên nếu là null/undefined. | { name: 'John' } | | isCloneDeep | boolean \| undefined | Không | true | true: deep clone data trước khi convert (an toàn). false: giữ nguyên reference (nhanh hơn, cẩn thận mutate). | false |

Cú pháp template:

{{ data | LibsUiPipesConvertObjectToSignalPipe }}
{{ data | LibsUiPipesConvertObjectToSignalPipe : false }}

Cú pháp standalone (TypeScript):

const signalObj = pipe.transform(data);
const signalObj = pipe.transform(data, false);

Kiểu dữ liệu đầu ra

// Pipe transform signature
transform<T>(data: T, isCloneDeep?: boolean): T

// Object → Nested WritableSignals
{ name: 'John', age: 30 }
=>
WritableSignal<{
  name: WritableSignal<string>,
  age: WritableSignal<number>
}>

// Array → WritableSignal<WritableSignal<T>[]>
['apple', 'banana']
=>
WritableSignal<[
  WritableSignal<string>,
  WritableSignal<string>
]>

Bảng hành vi theo kiểu đầu vào:

| Kiểu dữ liệu | Kết quả | |---|---| | null / undefined | Trả về nguyên (không convert) | | Primitive (string, number, boolean) | Wrapped trong WritableSignal<T> | | Plain object | WritableSignal<{ [key]: WritableSignal<V> }> — mỗi property là signal | | Array | WritableSignal<WritableSignal<T>[]> — mỗi phần tử là signal | | Nested object | Đệ quy — mọi cấp độ đều được convert | | Promise / Observable | Trả về nguyên (async objects không convert) | | Map / Set | Được xử lý đặc biệt theo logic trong convertObjectToSignal |

Lưu ý quan trọng

⚠️ Fine-grained Reactivity: Khác với signal({ name, email }) — khi dùng pipe này, thay đổi name không trigger re-render phần template dùng email. Đây là lợi thế chính để tối ưu hiệu năng.

⚠️ isCloneDeep = false: Khi tắt deep clone, mutate object gốc (data nguồn) cũng ảnh hưởng trực tiếp đến các signal. Chỉ dùng khi chắc chắn không có side effect từ code khác.

⚠️ @let trong template: Dùng @let để khai báo biến từ pipe trong template (Angular 18+). Với Angular < 18, cần dùng as trong *ngIf hoặc chuyển sang inject pipe trong TS.

⚠️ track trong @for với Array Signals: Khi dùng pipe với array, BẮT BUỘC track item (track signal reference bất biến) — KHÔNG dùng track $index.

<!-- ✅ ĐÚNG — track signal reference -->
@for (item of items(); track item) { ... }

<!-- ❌ SAI — BLOCKER -->
@for (item of items(); track $index) { ... }

⚠️ Không dùng trong computed(): Pipe gọi convertObjectToSignal là stateful. Không nên wrap kết quả pipe trong computed() vì mỗi lần computed chạy lại sẽ tạo signal mới, mất state đã set trước đó.

So sánh: Object Signal vs Nested Signals

// ❌ Object là một signal duy nhất — không hiệu quả
protected user = signal({ name: 'John', email: '[email protected]' });
// Khi thay đổi name:
this.user.set({ ...this.user(), name: 'Jane' });
// => Cả name VÀ email đều trigger re-render!

// ✅ Nested Signals — fine-grained
// Trong template:
// @let user = (userData | LibsUiPipesConvertObjectToSignalPipe)
// Hoặc trong TS:
protected userSig = this.convertPipe.transform({ name: 'John', email: '[email protected]' });
// Khi thay đổi name:
this.userSig().name.set('Jane');
// => Chỉ phần template dùng user().name() re-render, email() KHÔNG re-render

Demo

npx nx serve core-ui

Truy cập: http://localhost:4500/pipes/convert-object-to-signal