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/components-radio-single

v0.2.357-11

Published

> Component hiển thị một radio button đơn lẻ, hỗ trợ label, avatar, hình ảnh, popover và custom styling linh hoạt.

Readme

@libs-ui/components-radio-single

Component hiển thị một radio button đơn lẻ, hỗ trợ label, avatar, hình ảnh, popover và custom styling linh hoạt.

Giới thiệu

LibsUiComponentsRadioSingleComponent là một standalone Angular component dùng để hiển thị một radio button đơn. Component hoạt động theo mô hình two-way binding qua model<boolean>, cho phép parent kiểm soát trạng thái active và xử lý group logic bên ngoài. Component hỗ trợ đa dạng tùy chỉnh giao diện bao gồm label có i18n, avatar, bullet, icon tùy chỉnh, popover tooltip, và component outlet động.

Tính năng

  • Two-way binding — Trạng thái active dùng model<boolean>, hỗ trợ cả controlled và uncontrolled pattern.
  • Rich content — Hỗ trợ label (có i18n interpolation), avatar, hình ảnh, bullet, popover tooltip.
  • Click behavior linh hoạtclickExactly điều khiển vùng click: chỉ icon/label hoặc toàn bộ container.
  • Disable state — Hỗ trợ disable hoàn toàn hoặc chỉ disable label riêng biệt.
  • Image fallback — Tự động dùng linkImageError khi linkImage bị lỗi tải.
  • Component outlet — Có thể inject component động vào bên trong radio qua componentOutlet.
  • Custom styling — Cung cấp nhiều input class cho container, label, icon.
  • OnPush + Signals — Hiệu năng cao với Angular change detection tối ưu.

Khi nào sử dụng

  • Khi cần một radio button hoạt động độc lập mà không cần group logic phức tạp.
  • Khi muốn tùy chỉnh layout radio button kết hợp với avatar, hình ảnh, hoặc popover.
  • Khi xây dựng các component phức tạp hơn như Radio Group, Select List, hay option list custom.
  • Khi cần radio button hỗ trợ i18n translate cho label.

Cài đặt

npm install @libs-ui/components-radio-single

Import

import { LibsUiComponentsRadioSingleComponent } from '@libs-ui/components-radio-single';

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

Ví dụ sử dụng

1. Radio button cơ bản

// my.component.ts
import { Component, signal } from '@angular/core';
import { LibsUiComponentsRadioSingleComponent } from '@libs-ui/components-radio-single';
import { IRadioEvent } from '@libs-ui/components-radio-single';

@Component({
  standalone: true,
  imports: [LibsUiComponentsRadioSingleComponent],
  templateUrl: './my.component.html',
})
export class MyComponent {
  protected isActive = signal(false);

  protected handlerChange(event: IRadioEvent): void {
    event; // IRadioEvent: { active: boolean; key: unknown }
    this.isActive.set(event.active);
  }
}
<!-- my.component.html -->
<libs_ui-components-radio-single
  [active]="isActive()"
  (outChange)="handlerChange($event)"
  [key]="'option-1'"
  label="Tùy chọn 1"
/>

2. Radio button với disabled state

// my.component.ts
import { Component, signal } from '@angular/core';
import { LibsUiComponentsRadioSingleComponent } from '@libs-ui/components-radio-single';

@Component({
  standalone: true,
  imports: [LibsUiComponentsRadioSingleComponent],
  templateUrl: './my.component.html',
})
export class MyComponent {
  protected activeOption = signal(true);
  protected inactiveOption = signal(false);
}
<!-- my.component.html -->
<!-- Disabled và đang active -->
<libs_ui-components-radio-single
  [active]="activeOption()"
  [disable]="true"
  label="Đã chọn (vô hiệu hóa)"
/>

<!-- Disabled và không active -->
<libs_ui-components-radio-single
  [active]="inactiveOption()"
  [disable]="true"
  label="Chưa chọn (vô hiệu hóa)"
/>

3. Điều khiển vùng click bằng clickExactly

// my.component.ts
import { Component, signal } from '@angular/core';
import { LibsUiComponentsRadioSingleComponent } from '@libs-ui/components-radio-single';
import { IRadioEvent } from '@libs-ui/components-radio-single';

@Component({
  standalone: true,
  imports: [LibsUiComponentsRadioSingleComponent],
  templateUrl: './my.component.html',
})
export class MyComponent {
  protected clickAnywhereActive = signal(false);
  protected clickExactActive = signal(false);

  protected handlerClickAnywhere(event: IRadioEvent): void {
    event.active;
    this.clickAnywhereActive.set(event.active);
  }

  protected handlerClickExact(event: IRadioEvent): void {
    event.active;
    this.clickExactActive.set(event.active);
  }
}
<!-- my.component.html -->

<!-- clickExactly=false: click vào bất kỳ đâu trong container đều toggle -->
<libs_ui-components-radio-single
  [active]="clickAnywhereActive()"
  (outChange)="handlerClickAnywhere($event)"
  [clickExactly]="false"
  classInclude="flex items-center w-full"
  label="Click vào container sẽ toggle"
/>

<!-- clickExactly=true (mặc định): chỉ click đúng icon hoặc label mới toggle -->
<libs_ui-components-radio-single
  [active]="clickExactActive()"
  (outChange)="handlerClickExact($event)"
  [clickExactly]="true"
  classInclude="flex items-center w-full"
  label="Phải click đúng icon hoặc label"
/>

4. Radio button với Avatar

// my.component.ts
import { Component, signal } from '@angular/core';
import { LibsUiComponentsRadioSingleComponent } from '@libs-ui/components-radio-single';
import { IRadioEvent } from '@libs-ui/components-radio-single';
import { IAvatarConfig } from '@libs-ui/components-avatar';

@Component({
  standalone: true,
  imports: [LibsUiComponentsRadioSingleComponent],
  templateUrl: './my.component.html',
})
export class MyComponent {
  protected userActive = signal(false);

  protected readonly userAvatarConfig: IAvatarConfig = {
    linkAvatar: 'https://example.com/user-avatar.jpg',
    linkAvatarError: 'https://example.com/default-avatar.jpg',
    size: 32,
    typeShape: 'circle',
    textAvatar: 'ND',
    idGenColor: 'user-001',
  };

  protected handlerUserChange(event: IRadioEvent): void {
    event.key; // 'user-001'
    this.userActive.set(event.active);
  }
}
<!-- my.component.html -->
<libs_ui-components-radio-single
  [active]="userActive()"
  (outChange)="handlerUserChange($event)"
  [key]="'user-001'"
  [avatarConfig]="userAvatarConfig"
  label="Nguyễn Dũng"
  classLabelInclude="libs-ui-font-h4r ml-2"
/>

5. Radio button với i18n interpolation

// my.component.ts
import { Component, signal } from '@angular/core';
import { LibsUiComponentsRadioSingleComponent } from '@libs-ui/components-radio-single';
import { IRadioEvent } from '@libs-ui/components-radio-single';

@Component({
  standalone: true,
  imports: [LibsUiComponentsRadioSingleComponent],
  templateUrl: './my.component.html',
})
export class MyComponent {
  protected planActive = signal(false);

  protected readonly labelParams: Record<string, unknown> = {
    price: '99,000',
    currency: 'VND',
  };

  protected handlerPlanChange(event: IRadioEvent): void {
    event.active;
    this.planActive.set(event.active);
  }
}
<!-- my.component.html -->
<!-- label là i18n key, labelInterpolateParams truyền tham số vào translate pipe -->
<libs_ui-components-radio-single
  [active]="planActive()"
  (outChange)="handlerPlanChange($event)"
  [key]="'plan-basic'"
  label="i18n_plan_price_label"
  [labelInterpolateParams]="labelParams"
/>

6. Radio button trong group (pattern thường dùng)

// my.component.ts
import { Component, signal, computed } from '@angular/core';
import { LibsUiComponentsRadioSingleComponent } from '@libs-ui/components-radio-single';
import { IRadioEvent } from '@libs-ui/components-radio-single';

@Component({
  standalone: true,
  imports: [LibsUiComponentsRadioSingleComponent],
  templateUrl: './my.component.html',
})
export class MyComponent {
  protected selectedKey = signal<string>('option-a');

  protected readonly options = [
    { key: 'option-a', label: 'Tùy chọn A' },
    { key: 'option-b', label: 'Tùy chọn B' },
    { key: 'option-c', label: 'Tùy chọn C' },
  ];

  protected isActive = computed(() => (key: string) => this.selectedKey() === key);

  protected handlerChange(event: IRadioEvent): void {
    event.stopPropagation?.();
    this.selectedKey.set(event.key as string);
  }
}
<!-- my.component.html -->
@for (option of options; track option.key) {
  <libs_ui-components-radio-single
    [active]="selectedKey() === option.key"
    (outChange)="handlerChange($event)"
    [key]="option.key"
    [label]="option.label"
  />
}

@Input()

| Input | Type | Default | Mô tả | Ví dụ | |---|---|---|---|---| | [active] | model<boolean> | false | Trạng thái active (checked) của radio. Hỗ trợ two-way binding. | [active]="isActive()" hoặc [(active)]="isActive" | | [key] | unknown | undefined | Key định danh cho radio button, được emit kèm trong outChange. | [key]="'option-1'" | | [label] | string | '' | Label hiển thị bên cạnh radio. Hỗ trợ i18n key (qua TranslateModule). | label="Tùy chọn 1" | | [labelInterpolateParams] | Record<string, unknown> | {} | Tham số truyền vào translate pipe cho label i18n. | [labelInterpolateParams]="{ name: 'Admin' }" | | [disable] | boolean | false | Vô hiệu hóa hoàn toàn radio, không cho phép click hay toggle. | [disable]="true" | | [disableLabel] | boolean | false | Chỉ vô hiệu hóa visual của label (mờ), vẫn cho phép click icon. | [disableLabel]="true" | | [clickExactly] | boolean | true | true: chỉ click đúng icon/label mới toggle. false: click vào container cũng toggle. | [clickExactly]="false" | | [typeRadio] | 'normal' \| 'medium' | 'normal' | Kích thước/kiểu dáng của icon radio. | [typeRadio]="'medium'" | | [ignoreRadio] | boolean | undefined | Ẩn icon radio, chỉ hiển thị label/avatar/image. | [ignoreRadio]="true" | | [ignorePopoverLabel] | boolean | undefined | Không hiển thị popover của label dù đã cấu hình. | [ignorePopoverLabel]="true" | | [linkImage] | string | '' | URL hình ảnh hiển thị cạnh radio. | [linkImage]="'https://example.com/img.png'" | | [linkImageError] | string | '' | URL hình ảnh dự phòng khi linkImage tải thất bại. | [linkImageError]="'https://example.com/default.png'" | | [imgTypeIcon] | boolean | undefined | Nếu true, hình ảnh được cố định kích thước 18x18px (dùng cho icon nhỏ). | [imgTypeIcon]="true" | | [avatarConfig] | IAvatarConfig | undefined | Cấu hình hiển thị avatar bên cạnh radio. | [avatarConfig]="{ linkAvatar: '...', size: 32, typeShape: 'circle' }" | | [bullet] | Record<string, string> | undefined | Cấu hình bullet point (chấm màu) cạnh radio. Key backgroundColor là màu nền. | [bullet]="{ backgroundColor: '#22c55e' }" | | [popover] | IPopover | undefined | Cấu hình popover tooltip hiển thị kèm icon thông tin. | [popover]="{ config: { content: 'Mô tả thêm' } }" | | [zIndexLabel] | number | 1200 | z-index của label (dùng khi label nằm trong modal/dropdown). | [zIndexLabel]="1300" | | [classInclude] | string | '' | Custom CSS class thêm vào container ngoài cùng. | classInclude="flex items-center gap-2" | | [classLabelInclude] | string | 'libs-ui-font-h4r ' | Custom CSS class cho label. Ghi đè default font class. | classLabelInclude="libs-ui-font-h5r text-gray-600" | | [classIncludeIcon] | string | '' | Custom CSS class cho icon radio. | classIncludeIcon="text-blue-600" | | [classImageInclude] | string | '' | Custom CSS class cho thẻ <img>. | classImageInclude="rounded-full" | | [dataComponentOutlet] | TYPE_COMPONENT_OUTLET_DATA | undefined | Data truyền vào component được inject qua componentOutlet. | [dataComponentOutlet]="{ name: 'value' }" | | [componentOutlet] | any | undefined | Component động được inject vào bên trong radio qua ngComponentOutlet. | [componentOutlet]="MyBadgeComponent" |

@Output()

| Output | Type | Mô tả | Handler TS | Binding HTML | |---|---|---|---|---| | (outChange) | IRadioEvent | Emit khi trạng thái radio thay đổi (toggle active). Chỉ emit khi chưa active và không bị disable. | handlerChange(event: IRadioEvent): void { event.stopPropagation?.(); this.active.set(event.active); } | (outChange)="handlerChange($event)" | | (outClickLabel) | void | Emit khi người dùng click vào label. | handlerClickLabel(): void { /* xử lý click label */ } | (outClickLabel)="handlerClickLabel()" | | (outChangStageFlagMousePopover) | IFlagMouse | Emit trạng thái chuột khi hover/leave popover (dùng để đồng bộ trạng thái popover với parent). | handlerFlagMouse(flag: IFlagMouse): void { flag.stopPropagation?.(); this.flagMouse.set(flag); } | (outChangStageFlagMousePopover)="handlerFlagMouse($event)" |

Types & Interfaces

import { IRadioEvent, IRadioItem } from '@libs-ui/components-radio-single';
import { IAvatarConfig } from '@libs-ui/components-avatar';
import { IPopover } from '@libs-ui/components-popover';
// Event emit khi radio thay đổi trạng thái
export interface IRadioEvent {
  active: boolean;   // Trạng thái active mới sau khi thay đổi
  key: any;          // Key của radio button (từ input [key])
  item?: any;        // Data item đi kèm (tùy chọn, dùng khi integrate với list)
}

// Cấu hình cho từng item trong radio group (dùng khi xây Radio Group)
export interface IRadioItem {
  key: any;                                    // Key định danh bắt buộc
  active: boolean;                             // Trạng thái active
  classInclude?: string;                       // Custom class container
  label?: string;                              // Label hiển thị (hỗ trợ i18n key)
  labelInterpolateParams?: Record<string, unknown>; // Params cho translate
  ignorePopoverLabel?: boolean;                // Ẩn popover của label
  classLabelInclude?: string;                  // Custom class label
  popover?: IPopover;                          // Cấu hình popover
  disable?: boolean;                           // Vô hiệu hóa
  disableLabel?: boolean;                      // Vô hiệu hóa label
  clickExactly?: boolean;                      // Điều khiển vùng click
  zIndexLabel?: number;                        // z-index label
  avatarConfig?: IAvatarConfig;               // Cấu hình avatar
  data?: any;                                  // Data tùy ý đính kèm
  [key: string]: any;                          // Mở rộng thêm fields
}

Lưu ý quan trọng

⚠️ Logic nhóm radio phải xử lý ở parent: Component này là một radio button đơn lẻ, không tự tắt các radio khác. Parent component phải tự xử lý logic "chỉ một radio được active" bằng cách theo dõi selectedKey và truyền [active]="selectedKey() === item.key" cho từng radio.

⚠️ clickExactly mặc định là true: Mặc định người dùng phải click chính xác vào icon radio hoặc text label mới trigger toggle. Để click vào vùng container cũng trigger, cần truyền [clickExactly]="false" vào component.

⚠️ Radio không tự toggle lại về false: Một khi radio đã active = true, handlerClickchangeActive sẽ không làm gì (guard if (this.active()) trả về sớm). Đây là hành vi chuẩn của radio button — không cho phép bỏ chọn khi đã chọn. Nếu muốn toggle, cần xử lý bên ngoài.

⚠️ classLabelInclude ghi đè hoàn toàn: Khi truyền classLabelInclude, giá trị mặc định 'libs-ui-font-h4r ' sẽ bị thay thế hoàn toàn. Nếu muốn giữ typography mặc định và thêm class khác, cần include lại: classLabelInclude="libs-ui-font-h4r text-gray-600".

⚠️ Image fallback dùng AfterViewInit: Cơ chế fallback cho linkImageError chỉ hoạt động sau khi view khởi tạo. Nếu linkImage thay đổi dynamically, linkImageDisplay signal sẽ reset về linkImage mới nhờ effect(), nhưng listener lỗi chỉ được đăng ký một lần trong ngAfterViewInit.