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-signal-to-object

v0.2.357-11

Published

> Pipe chuyển đổi cấu trúc Signals lồng nhau thành plain object/array để sẵn sàng gửi API hoặc tích hợp thư viện bên ngoài.

Downloads

5,465

Readme

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

Pipe chuyển đổi cấu trúc Signals lồng nhau thành plain object/array để sẵn sàng gửi API hoặc tích hợp thư viện bên ngoài.

Giới thiệu

LibsUiPipesConvertSignalToObjectPipe nhận vào một cấu trúc signal lồng nhau (object hoặc array chứa các signal con) và đệ quy unwrap toàn bộ chúng thành plain object/array. Pipe này là cặp đôi ngược lại của @libs-ui/pipes-convert-object-to-signal, cho phép chuyển đổi hai chiều giữa dạng signal và plain object. Kết quả đầu ra là một plain object chuẩn, có thể serialize bằng JSON.stringify, gửi trực tiếp qua HTTP, hoặc truyền cho bất kỳ thư viện nào không hiểu Angular Signal.

Tính năng

  • ✅ Unwrap nested signals đệ quy nhiều cấp (multi-level)
  • ✅ Chuyển đổi Signal Object thành Plain Object
  • ✅ Chuyển đổi Signal Array thành Plain Array
  • ✅ Hỗ trợ Map và Set chứa signal
  • ✅ Hỗ trợ primitive signal (string, number, boolean)
  • ✅ Deep clone kết quả mặc định (bảo vệ signal gốc không bị mutate)
  • ✅ Tùy chọn tắt deep clone để tối ưu hiệu năng
  • ✅ Bỏ qua an toàn các đối tượng đặc biệt: Date, RegExp, DOM element, dayjs...
  • ✅ Chống vòng lặp vô hạn với circular reference detection

Khi nào sử dụng

  • Cần gửi state dạng signal lên API (HTTP POST/PUT) — các HTTP client yêu cầu plain object
  • Cần serialize state sang JSON.stringify để lưu vào localStorage / sessionStorage
  • Tích hợp với thư viện bên ngoài (chart, map, form lib...) chỉ nhận plain object
  • Cần log hoặc debug giá trị thực bên trong một cấu trúc signal phức tạp
  • Chuẩn bị payload trước khi submit form chứa các signal field

Cài đặt

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

Import

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

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

Ví dụ sử dụng

1. Unwrap signal object thành plain object (template)

import { Component, signal } from '@angular/core';
import { LibsUiPipesConvertSignalToObjectPipe } from '@libs-ui/pipes-convert-signal-to-object';

@Component({
  standalone: true,
  imports: [LibsUiPipesConvertSignalToObjectPipe],
  template: `
    @let user = (userSignal | LibsUiPipesConvertSignalToObjectPipe);
    <p>Tên: {{ user.name }}</p>
    <p>Email: {{ user.email }}</p>
    <button (click)="handlerSave(user)">Lưu lên API</button>
  `,
})
export class UserFormComponent {
  readonly userSignal = signal({
    name: signal('Nguyễn Văn A'),
    email: signal('[email protected]'),
    age: signal(28),
  });

  handlerSave(payload: { name: string; email: string; age: number }): void {
    // payload đã là plain object, sẵn sàng gửi API
    // this.http.post('/api/users', payload).subscribe(...)
  }
}

2. Unwrap signal array thành plain array

import { Component, signal } from '@angular/core';
import { LibsUiPipesConvertSignalToObjectPipe } from '@libs-ui/pipes-convert-signal-to-object';

@Component({
  standalone: true,
  imports: [LibsUiPipesConvertSignalToObjectPipe],
  template: `
    @let items = (itemsSignal | LibsUiPipesConvertSignalToObjectPipe);
    <ul>
      @for (item of items; track item) {
        <li>{{ item }}</li>
      }
    </ul>
    <p>JSON: {{ items | json }}</p>
  `,
})
export class ItemListComponent {
  readonly itemsSignal = signal([
    signal('Táo'),
    signal('Chuối'),
    signal('Cam'),
  ]);
}

3. Unwrap nested signal nhiều cấp

import { Component, signal } from '@angular/core';
import { LibsUiPipesConvertSignalToObjectPipe } from '@libs-ui/pipes-convert-signal-to-object';

@Component({
  standalone: true,
  imports: [LibsUiPipesConvertSignalToObjectPipe],
  template: `
    @let company = (companySignal | LibsUiPipesConvertSignalToObjectPipe);
    <p>Công ty: {{ company.name }}</p>
    <p>Giám đốc: {{ company.ceo.name }} ({{ company.ceo.age }} tuổi)</p>
    <pre>{{ company | json }}</pre>
  `,
})
export class CompanyDetailComponent {
  readonly companySignal = signal({
    name: signal('Tech Corp'),
    ceo: signal({
      name: signal('Trần Văn B'),
      age: signal(45),
    }),
  });

  // companySignal | LibsUiPipesConvertSignalToObjectPipe
  // => { name: 'Tech Corp', ceo: { name: 'Trần Văn B', age: 45 } }
}

4. Tắt deep clone để tăng hiệu năng

import { Component, signal } from '@angular/core';
import { LibsUiPipesConvertSignalToObjectPipe } from '@libs-ui/pipes-convert-signal-to-object';

@Component({
  standalone: true,
  imports: [LibsUiPipesConvertSignalToObjectPipe],
  template: `
    <!-- Truyền false để bỏ qua deep clone — nhanh hơn nhưng không sao chép -->
    @let rawData = (dataSignal | LibsUiPipesConvertSignalToObjectPipe : false);
    <p>{{ rawData.title }}</p>
  `,
})
export class FastReadComponent {
  readonly dataSignal = signal({
    title: signal('Báo cáo tháng 6'),
    count: signal(120),
  });
}

5. Sử dụng độc lập trong TypeScript (pipe.transform)

import { Component, inject, signal } from '@angular/core';
import { LibsUiPipesConvertSignalToObjectPipe } from '@libs-ui/pipes-convert-signal-to-object';

@Component({
  standalone: true,
  imports: [LibsUiPipesConvertSignalToObjectPipe],
  providers: [LibsUiPipesConvertSignalToObjectPipe],
  template: `<button (click)="handlerSubmit()">Gửi form</button>`,
})
export class OrderFormComponent {
  private readonly convertPipe = inject(LibsUiPipesConvertSignalToObjectPipe);

  readonly orderSignal = signal({
    productId: signal('P001'),
    quantity: signal(5),
    note: signal('Giao buổi sáng'),
  });

  handlerSubmit(): void {
    const payload = this.convertPipe.transform(this.orderSignal);
    // payload = { productId: 'P001', quantity: 5, note: 'Giao buổi sáng' }
    // this.orderApi.create(payload).subscribe(...)
  }
}

6. Lưu state vào localStorage

import { Component, inject, signal } from '@angular/core';
import { LibsUiPipesConvertSignalToObjectPipe } from '@libs-ui/pipes-convert-signal-to-object';

@Component({
  standalone: true,
  imports: [LibsUiPipesConvertSignalToObjectPipe],
  providers: [LibsUiPipesConvertSignalToObjectPipe],
  template: `<button (click)="handlerSaveFilter()">Lưu bộ lọc</button>`,
})
export class FilterBarComponent {
  private readonly convertPipe = inject(LibsUiPipesConvertSignalToObjectPipe);

  readonly filterSignal = signal({
    keyword: signal('angular'),
    status: signal('active'),
    page: signal(1),
  });

  handlerSaveFilter(): void {
    const plainFilter = this.convertPipe.transform(this.filterSignal);
    // plainFilter = { keyword: 'angular', status: 'active', page: 1 }
    localStorage.setItem('user-filter', JSON.stringify(plainFilter));
  }
}

Transform

| Tham số | Type | Bắt buộc | Default | Mô tả | |---|---|---|---|---| | data | any | Có | — | Giá trị signal hoặc cấu trúc object/array chứa nested signals cần unwrap | | isCloneDeep | boolean | Không | true | true để deep clone kết quả (bảo vệ signal gốc); false để giữ reference gốc (nhanh hơn, cẩn thận mutate) |

Cú pháp template:

{{ dataSignal | LibsUiPipesConvertSignalToObjectPipe }}
{{ dataSignal | LibsUiPipesConvertSignalToObjectPipe : false }}
@let plain = (dataSignal | LibsUiPipesConvertSignalToObjectPipe);

Cú pháp standalone (inject):

const plain = this.convertPipe.transform(this.dataSignal);
const plainNoClone = this.convertPipe.transform(this.dataSignal, false);

Kiểu trả về: Plain object, plain array, hoặc primitive value sau khi đã unwrap toàn bộ signal con.

Hành vi chi tiết

Unwrap đệ quy so với gọi ()

// Gọi () chỉ unwrap 1 cấp — vẫn còn signal con bên trong
userSignal()
// => { name: WritableSignal<string>, age: WritableSignal<number> }

// Pipe unwrap toàn bộ tất cả cấp
userSignal | LibsUiPipesConvertSignalToObjectPipe
// => { name: 'Nguyễn Văn A', age: 28 }

Quy tắc xử lý theo kiểu dữ liệu

| Kiểu input | Hành vi | |---|---| | Signal<primitive> | Unwrap thành giá trị nguyên thủy | | Signal<object> | Unwrap object, tiếp tục đệ quy vào các property | | Signal<array> | Unwrap array, tiếp tục đệ quy từng phần tử | | Signal<Map> | Unwrap thành Map mới với values đã unwrap | | Signal<Set> | Unwrap thành Set mới với values đã unwrap | | null / undefined | Trả về nguyên như đầu vào, không throw | | Date, RegExp, DOM element, dayjs | Giữ nguyên, không xử lý | | Circular reference | Được phát hiện và xử lý an toàn, không bị stack overflow |

Deep clone (mặc định true)

// isCloneDeep = true (mặc định) — AN TOÀN
const plain = this.convertPipe.transform(this.dataSignal);
plain.name = 'Jane'; // Không ảnh hưởng đến this.dataSignal

// isCloneDeep = false — NHANH HƠN nhưng cần cẩn thận
const plainRef = this.convertPipe.transform(this.dataSignal, false);
plainRef.name = 'Jane'; // Có thể ảnh hưởng đến giá trị bên trong signal gốc

Types & Interfaces

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

// Pipe transform signature
// transform(data: any, isCloneDeep?: boolean): any

// Lưu ý: transform(data: any, isCloneDeep?: boolean): any KHÔNG phải generic method
// — không được viết transform<T>(...), TypeScript sẽ báo lỗi compile.
// Muốn có type rõ ràng, ép kiểu (cast) kết quả trả về:
const result = this.convertPipe.transform(this.userSignal) as { name: string; age: number };

Lưu ý quan trọng

⚠️ Không serialize trực tiếp Signal: JSON.stringify(signal) sẽ không cho kết quả đúng vì signal là function object. Luôn dùng pipe này trước khi serialize.

⚠️ isCloneDeep = false và mutation: Khi tắt deep clone, việc thay đổi property trên plain object kết quả có thể làm thay đổi giá trị bên trong signal gốc không mong muốn. Chỉ dùng false khi chắc chắn không mutate kết quả.

⚠️ Đối tượng đặc biệt được giữ nguyên: Date, RegExp, dayjs, DOM element và các đối tượng framework sẽ được truyền nguyên vẹn mà không bị unwrap, đảm bảo không mất dữ liệu.

⚠️ Pipe chỉ unwrap — không làm reactive: Kết quả trả về là snapshot tại thời điểm pipe chạy. Nếu signal thay đổi sau đó, cần pipe chạy lại (Angular change detection sẽ tự kích hoạt trong template với OnPush khi signal thay đổi).

⚠️ Dùng với inject khi cần trong TypeScript: Để dùng ngoài template, khai báo trong providers của component và inject bằng inject(LibsUiPipesConvertSignalToObjectPipe).