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-inputs-multi-language

v0.2.357-4

Published

> Component nhập liệu đa ngôn ngữ và đa tiền tệ cho Angular, hỗ trợ nhiều kiểu input và validation tích hợp.

Readme

@libs-ui/components-inputs-multi-language

Component nhập liệu đa ngôn ngữ và đa tiền tệ cho Angular, hỗ trợ nhiều kiểu input và validation tích hợp.

Giới thiệu

@libs-ui/components-inputs-multi-language cung cấp hai component chính: LibsUiComponentsInputsMultiLanguageComponent để nhập liệu cho một trường dữ liệu trên nhiều ngôn ngữ/tiền tệ, và LibsUiComponentsInputsMultiLanguageItemsComponent để quản lý nhiều khối nhập liệu có thể thêm/xóa động. Cả hai component đều hỗ trợ validation tích hợp, giao tiếp qua FunctionsControl getter, và hoạt động với Angular Signals + OnPush change detection.

Tính năng

  • ✅ Nhập liệu đồng thời nhiều ngôn ngữ (vi, en, ...) hoặc nhiều tiền tệ (VND, USD, EUR, ...)
  • ✅ Hỗ trợ 6 kiểu view: text, integer, number, float, bigint, editor (Quill rich text)
  • ✅ Thêm/xóa ngôn ngữ động qua giao diện người dùng
  • ✅ Validation tích hợp: required, min length, max length, pattern, đếm ký tự
  • ✅ Two-way binding qua Angular model() Signal API ([(dataLanguage)])
  • ✅ Chế độ đơn ngôn ngữ (singleLanguage) để chỉ hiển thị ngôn ngữ mặc định
  • ✅ Chế độ chỉ đọc (readonly) và chế độ xem nội dung (viewContent)
  • ✅ Layout linh hoạt: hàng ngang (row) hoặc cột dọc (column)
  • ✅ API FunctionsControl để validate và lấy dữ liệu từ component cha qua ViewChild
  • LibsUiComponentsInputsMultiLanguageItemsComponent quản lý nhiều khối nhập liệu có thể thêm/xóa

Khi nào sử dụng

  • Khi xây dựng form quản trị nội dung hỗ trợ đa ngôn ngữ (tên sản phẩm, mô tả, tiêu đề banner)
  • Khi cần nhập đồng thời giá trị cho nhiều tiền tệ (VND, USD, EUR) trên cùng một trường
  • Khi cần trình soạn thảo văn bản giàu tính năng (Quill) cho nội dung đa ngôn ngữ
  • Khi có nhiều "khối" cấu hình lặp lại (nhiều banner, nhiều biến thể sản phẩm) mỗi khối cần nhập đa ngôn ngữ
  • Khi cần validate dữ liệu đa ngôn ngữ theo logic: nếu nhập một ngôn ngữ phụ thì ngôn ngữ mặc định bắt buộc phải có

Cài đặt

npm install @libs-ui/components-inputs-multi-language

Import

// Component nhập liệu đa ngôn ngữ đơn trường
import { LibsUiComponentsInputsMultiLanguageComponent } from '@libs-ui/components-inputs-multi-language';

// Component quản lý nhiều khối nhập liệu
import { LibsUiComponentsInputsMultiLanguageItemsComponent } from '@libs-ui/components-inputs-multi-language';

// Interfaces và types
import {
  IDataItem,
  IDataMultiLanguage,
  IDataLanguage,
  IEventMultiLanguage,
  IConfigMultiLanguageHeader,
  IInputMultiLanguageFunctionControlEvent,
  IOption,
  ICurrencyUnit,
  TYPE_VIEW_MULTI_LANGUAGE,
} from '@libs-ui/components-inputs-multi-language';

Ví dụ sử dụng

Ví dụ 1 — Nhập liệu văn bản cơ bản (text)

import { Component, signal, viewChild } from '@angular/core';
import {
  LibsUiComponentsInputsMultiLanguageComponent,
  IDataMultiLanguage,
} from '@libs-ui/components-inputs-multi-language';

@Component({
  selector: 'app-product-form',
  standalone: true,
  changeDetection: ChangeDetectionStrategy.OnPush,
  imports: [LibsUiComponentsInputsMultiLanguageComponent],
  template: `
    <libs_ui-components-inputs-multi_language
      #nameField
      [(dataLanguage)]="productName"
      [titleField]="'Tên sản phẩm'"
      [placeholder]="'Nhập tên sản phẩm...'"
      [validRequired]="{ isRequired: true, message: 'Tên sản phẩm là bắt buộc' }"
      (outChangeValueInput)="handlerNameChange()"
    />

    <button (click)="handlerSave()">Lưu</button>
  `,
})
export class ProductFormComponent {
  protected productName = signal<IDataMultiLanguage>({
    vi: 'Áo thun nam',
    en: 'Men T-Shirt',
  });

  private readonly nameFieldRef = viewChild<LibsUiComponentsInputsMultiLanguageComponent>('nameField');

  protected handlerNameChange(): void {
    // xử lý khi giá trị thay đổi
  }

  protected async handlerSave(): Promise<void> {
    const isValid = await this.nameFieldRef()?.FunctionsControl.checkIsValid();
    if (!isValid) {
      return;
    }
    const data = this.nameFieldRef()?.FunctionsControl.getData();
    // gửi data lên API
  }
}

Ví dụ 2 — Nhập liệu tiền tệ (integer)

import { Component, signal } from '@angular/core';
import {
  LibsUiComponentsInputsMultiLanguageComponent,
  IDataMultiLanguage,
} from '@libs-ui/components-inputs-multi-language';

@Component({
  selector: 'app-price-form',
  standalone: true,
  changeDetection: ChangeDetectionStrategy.OnPush,
  imports: [LibsUiComponentsInputsMultiLanguageComponent],
  template: `
    <libs_ui-components-inputs-multi_language
      [viewType]="'integer'"
      [(dataLanguage)]="priceData"
      [titleField]="'Giá bán lẻ'"
      [placeholder]="'0'"
      [validRequired]="{ isRequired: true, message: 'Vui lòng nhập giá' }"
    />
  `,
})
export class PriceFormComponent {
  protected priceData = signal<IDataMultiLanguage>({
    VND: 1500000,
    USD: 60,
  });
}

Ví dụ 3 — Trình soạn thảo Quill (editor)

import { Component, signal } from '@angular/core';
import {
  LibsUiComponentsInputsMultiLanguageComponent,
  IDataMultiLanguage,
} from '@libs-ui/components-inputs-multi-language';

@Component({
  selector: 'app-description-form',
  standalone: true,
  changeDetection: ChangeDetectionStrategy.OnPush,
  imports: [LibsUiComponentsInputsMultiLanguageComponent],
  template: `
    <libs_ui-components-inputs-multi_language
      [viewType]="'editor'"
      [(dataLanguage)]="descriptionData"
      [titleField]="'Mô tả chi tiết'"
      [viewPosition]="'column'"
    />
  `,
})
export class DescriptionFormComponent {
  protected descriptionData = signal<IDataMultiLanguage>({
    vi: '<p>Mô tả sản phẩm <strong>chi tiết</strong>.</p>',
    en: '<p>Detailed product <strong>description</strong>.</p>',
  });
}

Ví dụ 4 — Nhiều khối nhập liệu (Items)

import { Component, signal, viewChild } from '@angular/core';
import {
  LibsUiComponentsInputsMultiLanguageItemsComponent,
  IDataItem,
  IDataMultiLanguage,
} from '@libs-ui/components-inputs-multi-language';

@Component({
  selector: 'app-banner-form',
  standalone: true,
  changeDetection: ChangeDetectionStrategy.OnPush,
  imports: [LibsUiComponentsInputsMultiLanguageItemsComponent],
  template: `
    <libs_ui-components-inputs-multi_language-items
      #bannerItems
      [(dataMultiLanguage)]="bannerRows"
      [configItems]="bannerConfig"
      [labelAddItem]="'Thêm banner'"
      (outChangeValue)="handlerBannerChange()"
    />

    <button (click)="handlerSave()">Lưu</button>
  `,
})
export class BannerFormComponent {
  protected bannerRows = signal<IDataMultiLanguage[]>([
    {
      title: { vi: 'Banner mùa hè', en: 'Summer banner' },
      content: { vi: 'Khuyến mãi lớn', en: 'Big sale' },
    },
  ]);

  protected readonly bannerConfig: IDataItem[] = [
    {
      keyBindData: 'content',
      type: 'text',
      titleField: 'Nội dung banner',
      header: {
        title: 'Tiêu đề banner',
        keyBindData: 'title',
        type: 'text',
        dataDefault: {
          title: { vi: '', en: '' },
          content: { vi: '', en: '' },
        },
      },
    },
  ];

  private readonly bannerItemsRef =
    viewChild<LibsUiComponentsInputsMultiLanguageItemsComponent>('bannerItems');

  protected handlerBannerChange(): void {
    // xử lý khi giá trị thay đổi
  }

  protected async handlerSave(): Promise<void> {
    const isValid = await this.bannerItemsRef()?.FunctionsControl.checkIsValid();
    if (!isValid) {
      return;
    }
    const data = this.bannerItemsRef()?.FunctionsControl.getData();
    // gửi data lên API
  }
}

Ví dụ 5 — Chế độ đơn ngôn ngữ và chỉ đọc

import { Component, signal } from '@angular/core';
import {
  LibsUiComponentsInputsMultiLanguageComponent,
  IDataMultiLanguage,
} from '@libs-ui/components-inputs-multi-language';

@Component({
  selector: 'app-detail-view',
  standalone: true,
  changeDetection: ChangeDetectionStrategy.OnPush,
  imports: [LibsUiComponentsInputsMultiLanguageComponent],
  template: `
    <!-- Chỉ đọc -->
    <libs_ui-components-inputs-multi_language
      [dataLanguage]="titleData()"
      [readonly]="true"
      [titleField]="'Tên sản phẩm'"
    />

    <!-- Đơn ngôn ngữ (chỉ nhập ngôn ngữ mặc định) -->
    <libs_ui-components-inputs-multi_language
      [(dataLanguage)]="titleData"
      [singleLanguage]="true"
      [titleField]="'Tên sản phẩm (đơn ngôn ngữ)'"
    />
  `,
})
export class DetailViewComponent {
  protected titleData = signal<IDataMultiLanguage>({
    vi: 'Áo thun cao cấp',
    en: 'Premium T-Shirt',
  });
}

@Input() — LibsUiComponentsInputsMultiLanguageComponent

| Input | Type | Default | Mô tả | Ví dụ | |---|---|---|---|---| | acceptNegativeValue | boolean | undefined | Cho phép nhập giá trị số âm | [acceptNegativeValue]="true" | | classIconAddLanguage | string | 'libs-ui-icon-add' | Class icon cho nút thêm ngôn ngữ | [classIconAddLanguage]="'libs-ui-icon-add'" | | classIncludeKey | string | 'libs-ui-font-h4r' | Class CSS cho phần hiển thị nhãn key (ngôn ngữ) | [classIncludeKey]="'libs-ui-font-h5r'" | | classIncludeValue | string | 'libs-ui-font-h4s' | Class CSS cho phần hiển thị giá trị | [classIncludeValue]="'libs-ui-font-h5s'" | | countCharacters | boolean | undefined | Hiển thị bộ đếm ký tự bên dưới input | [countCharacters]="true" | | data | Array<IDataItem> | [] | (model) Cấu hình các item nhập liệu (tự động sinh nếu không truyền) | [(data)]="itemsConfig" | | dataLanguage | IDataMultiLanguage | {} | (model) Dữ liệu ngôn ngữ/tiền tệ, dùng two-way binding | [(dataLanguage)]="myData" | | extendClass | string | undefined | (model) Class CSS mở rộng cho container chính | [(extendClass)]="'mt-4'" | | extendClassContent | string | undefined | Class CSS mở rộng cho vùng nội dung input | [extendClassContent]="'p-2'" | | ignoreAdd | boolean | undefined | (model) Ẩn nút thêm ngôn ngữ mới | [(ignoreAdd)]="hideAdd" | | ignoreRemove | boolean | undefined | Ẩn nút xóa ngôn ngữ | [ignoreRemove]="true" | | keyHeader | string | undefined | Key dùng để xác định header trong dữ liệu | [keyHeader]="'title'" | | maxValueNumber | number | undefined | Giá trị tối đa cho input kiểu số | [maxValueNumber]="9999999" | | onlyAcceptNegativeValue | boolean | undefined | Chỉ chấp nhận giá trị âm | [onlyAcceptNegativeValue]="true" | | optionsLanguage | Array<IOption> | langOptions() | Danh sách tùy chọn ngôn ngữ/tiền tệ để hiển thị trong dropdown | [optionsLanguage]="customLangs" | | placeholder | string | undefined | Placeholder cho tất cả các input bên trong | [placeholder]="'Nhập nội dung...'" | | readonly | boolean | undefined | Chế độ chỉ đọc, không cho phép chỉnh sửa | [readonly]="true" | | singleLanguage | boolean | undefined | Chỉ hiển thị ngôn ngữ mặc định đầu tiên, ẩn lựa chọn ngôn ngữ | [singleLanguage]="true" | | textArea | boolean | undefined | Dùng textarea nhiều dòng thay vì input một dòng | [textArea]="true" | | titleField | string | undefined | Nhãn tiêu đề hiển thị phía trên vùng nhập liệu | [titleField]="'Tên sản phẩm'" | | validMaxLength | IValidLength | { length: 0 } | Cấu hình độ dài ký tự tối đa (0 = không giới hạn) | [validMaxLength]="{ length: 500, message: 'Tối đa 500 ký tự' }" | | validMinLength | IValidLength | undefined | Cấu hình độ dài ký tự tối thiểu | [validMinLength]="{ length: 10, message: 'Tối thiểu 10 ký tự' }" | | validOneDefaultMultiLanguage | string | undefined | Key ngôn ngữ mặc định bắt buộc có giá trị nếu có bất kỳ ngôn ngữ nào khác được nhập | [validOneDefaultMultiLanguage]="'vi'" | | validPattern | Array<IValidPattern> | undefined | Danh sách regex pattern dùng để validate | [validPattern]="patternRules" | | validRequired | IValidRequired | { isRequired: false, message: '' } | Cấu hình validation bắt buộc nhập | [validRequired]="{ isRequired: true, message: 'Bắt buộc nhập' }" | | valueUpDownNumber | number | undefined | Bước nhảy khi bấm nút tăng/giảm với input kiểu số | [valueUpDownNumber]="1000" | | viewContent | boolean | undefined | Chỉ hiển thị nội dung dạng text, không render input | [viewContent]="true" | | viewPosition | 'row' \| 'column' | 'row' | Hướng hiển thị của nhãn và vùng input | [viewPosition]="'column'" | | viewType | TYPE_VIEW_MULTI_LANGUAGE | 'text' | Kiểu input: 'text' | 'integer' | 'number' | 'float' | 'bigint' | 'editor' | [viewType]="'integer'" | | zIndex | number | undefined | z-index cho dropdown/popover bên trong | [zIndex]="1050" |

@Output() — LibsUiComponentsInputsMultiLanguageComponent

| Output | Type | Mô tả | Handler TS | Binding HTML | |---|---|---|---|---| | (outChangeTypeLanguage) | boolean | Phát ra khi chuyển đổi chế độ đơn/đa ngôn ngữ | handlerChangeTypeLanguage(event: boolean): void { event.stopPropagation(); /* xử lý */ } | (outChangeTypeLanguage)="handlerChangeTypeLanguage($event)" | | (outChangeValueInput) | void | Phát ra khi giá trị trong bất kỳ input nào thay đổi (debounce 500ms) | handlerValueChange(): void { /* xử lý */ } | (outChangeValueInput)="handlerValueChange()" | | (outEventMultiLanguage) | IEventMultiLanguage | Phát ra khi người dùng thêm hoặc xóa một ngôn ngữ | handlerLangEvent(event: IEventMultiLanguage): void { event.stopPropagation(); /* xử lý action: 'add' | 'remove' */ } | (outEventMultiLanguage)="handlerLangEvent($event)" |

FunctionsControl — LibsUiComponentsInputsMultiLanguageComponent

Truy cập qua ViewChild với getter FunctionsControl:

private readonly fieldRef = viewChild<LibsUiComponentsInputsMultiLanguageComponent>('fieldRef');

// Validate tất cả input bên trong
const isValid = await this.fieldRef()?.FunctionsControl.checkIsValid(); // Promise<boolean>

// Lấy dữ liệu hiện tại
const data = this.fieldRef()?.FunctionsControl.getData(); // IDataMultiLanguage | IDataMultiLanguage[] | undefined

@Input() — LibsUiComponentsInputsMultiLanguageItemsComponent

| Input | Type | Default | Mô tả | Ví dụ | |---|---|---|---|---| | acceptNegativeValue | boolean | undefined | Cho phép số âm trong input số | [acceptNegativeValue]="true" | | classIconAddLanguage | string | 'libs-ui-icon-add' | Class icon cho nút thêm ngôn ngữ trong các component con | [classIconAddLanguage]="'libs-ui-icon-add'" | | configItems | Array<IDataItem> | [] | Cấu hình các cột; phần tử đầu tiên chứa header cho dòng tiêu đề | [configItems]="bannerConfig" | | dataMultiLanguage | Array<IDataMultiLanguage> | [{}] | (model) Danh sách các khối dữ liệu | [(dataMultiLanguage)]="bannerRows" | | ignoreAdd | boolean | undefined | Ẩn nút thêm khối mới | [ignoreAdd]="true" | | labelAddItem | string | undefined | Nhãn nút thêm khối mới | [labelAddItem]="'Thêm banner'" | | maxValueNumber | number | undefined | Giá trị tối đa cho input kiểu số | [maxValueNumber]="100" | | onlyAcceptNegativeValue | boolean | undefined | Chỉ chấp nhận số âm | [onlyAcceptNegativeValue]="true" | | singleLanguage | boolean | undefined | Truyền xuống component con: chỉ hiển thị ngôn ngữ mặc định | [singleLanguage]="true" | | valueUpDownNumber | number | undefined | Bước nhảy tăng/giảm cho input số | [valueUpDownNumber]="1" | | zIndex | number | undefined | z-index cho overlay bên trong | [zIndex]="1050" |

@Output() — LibsUiComponentsInputsMultiLanguageItemsComponent

| Output | Type | Mô tả | Handler TS | Binding HTML | |---|---|---|---|---| | (outChangeValue) | void | Phát ra khi giá trị header hoặc bất kỳ input đa ngôn ngữ nào thay đổi | handlerItemsChange(): void { /* xử lý */ } | (outChangeValue)="handlerItemsChange()" |

FunctionsControl — LibsUiComponentsInputsMultiLanguageItemsComponent

private readonly itemsRef = viewChild<LibsUiComponentsInputsMultiLanguageItemsComponent>('itemsRef');

// Validate tất cả khối
const isValid = await this.itemsRef()?.FunctionsControl.checkIsValid(); // Promise<boolean>

// Lấy toàn bộ dữ liệu các khối
const rows = this.itemsRef()?.FunctionsControl.getData(); // IDataMultiLanguage[] | IDataMultiLanguage | undefined

Types & Interfaces

import {
  IDataItem,
  IDataMultiLanguage,
  IDataLanguage,
  IEventMultiLanguage,
  IConfigMultiLanguageHeader,
  IInputMultiLanguageFunctionControlEvent,
  IOption,
  ICurrencyUnit,
  TYPE_VIEW_MULTI_LANGUAGE,
} from '@libs-ui/components-inputs-multi-language';

/** Kiểu view được hỗ trợ */
export type TYPE_VIEW_MULTI_LANGUAGE = 'text' | 'integer' | 'number' | 'editor' | 'float' | 'bigint';

/** Dữ liệu đa ngôn ngữ: key là mã ngôn ngữ/tiền tệ, value là chuỗi, số hoặc object lồng nhau */
export interface IDataMultiLanguage {
  [param: string]: IDataLanguage | string | number;
}

/** Dữ liệu ngôn ngữ lồng nhau (cho cấu trúc items) */
export interface IDataLanguage {
  [param: string]: string | number;
}

/** Cấu hình một item nhập liệu */
export interface IDataItem {
  keyBindData?: string;          // key để bind vào object dữ liệu
  placeholder?: string;          // placeholder cho input
  type?: string;                 // kiểu input (text, integer, editor, ...)
  header?: IConfigMultiLanguageHeader; // cấu hình dòng header (dùng trong Items)
  isOneValue?: boolean;          // chỉ có 1 giá trị
  classIncludeInput?: string;    // class CSS cho input
  titleField?: string;           // nhãn tiêu đề
  isCountCharacters?: boolean;   // hiển thị đếm ký tự
  validRequired?: IValidRequired;
  dataMultiLanguage?: IDataMultiLanguage;
  validRequiredLangDefault?: IValidRequired;
  validRequiredDefault?: IValidRequired;
  minHeightEditorContentDefault?: number; // chiều cao tối thiểu editor (px)
  maxHeightEditorContentDefault?: number; // chiều cao tối đa editor (px)
  heightEditorContentDefault?: number;    // chiều cao mặc định editor (px)
  resize?: 'vertical' | 'none';          // cho phép resize textarea
}

/** Cấu hình dòng header trong LibsUiComponentsInputsMultiLanguageItemsComponent */
export interface IConfigMultiLanguageHeader {
  title?: string;            // nhãn hiển thị cho header
  isShowInput?: boolean;     // có hiển thị input không
  validRequired?: {
    isRequired?: boolean;
  };
  keyBindData?: string;      // key trỏ tới field header trong dữ liệu
  dataDefault?: IDataMultiLanguage | { total?: IDataMultiLanguage }; // dữ liệu mặc định khi thêm khối mới
  type?: string;             // kiểu input của header
}

/** Event phát ra khi thêm/xóa ngôn ngữ */
export interface IEventMultiLanguage {
  action: 'add' | 'remove';
  data: IDataMultiLanguage;
}

/** API FunctionsControl */
export interface IInputMultiLanguageFunctionControlEvent {
  checkIsValid: () => Promise<boolean>;
  getData: () => IDataMultiLanguage[] | IDataMultiLanguage | undefined;
}

/** Một tùy chọn ngôn ngữ/tiền tệ */
export interface IOption {
  label: string; // tên hiển thị (ví dụ: 'Tiếng Việt', 'USD')
  key: string;   // mã định danh (ví dụ: 'vi', 'USD')
}

/** Đơn vị tiền tệ */
export interface ICurrencyUnit {
  label: string;
  id: string;
  type: string;
}

Lưu ý quan trọng

⚠️ Selector với dấu gạch dưới: Selector của component dùng dấu gạch dưới theo quy ước libs-ui. Dùng đúng libs_ui-components-inputs-multi_language (không phải libs-ui-components-inputs-multi-language).

⚠️ viewType = 'integer' tự động chuyển sang tiền tệ: Khi viewType'integer', component tự động thay danh sách ngôn ngữ bằng danh sách tiền tệ hỗ trợ (lấy từ UtilsCache). Dữ liệu dataLanguage lúc này có dạng { VND: 1500000, USD: 60 }.

⚠️ Danh sách ngôn ngữ/tiền tệ hỗ trợ từ UtilsCache: Danh sách ngôn ngữ mặc định lấy từ UtilsCache.Get('test_lang', ['vi', 'en']). Để tùy chỉnh, truyền [optionsLanguage] với mảng IOption[] của riêng bạn.

⚠️ FunctionsControl qua ViewChild: Để validate và lấy dữ liệu từ component cha, dùng viewChild() (Signal API) với template variable, rồi truy cập getter FunctionsControl — không gọi method trực tiếp trên component.

⚠️ Validation thông minh: Khi có nhiều ngôn ngữ và validRequired không được bật, component tự động bật required cho ngôn ngữ mặc định nếu người dùng đã nhập bất kỳ ngôn ngữ phụ nào. Logic này chạy tự động với debounce 500ms.

⚠️ IDataItem.header trong Items: Trong LibsUiComponentsInputsMultiLanguageItemsComponent, configItems[0].header.dataDefault xác định cấu trúc dữ liệu mặc định khi thêm khối mới. Cấu trúc này PHẢI khớp với cấu trúc của dataMultiLanguage.

⚠️ Two-way binding với model(): Cả data, dataLanguage, extendClass, ignoreAdd đều là model() signal — dùng cú pháp [(xxx)]="signal" để two-way binding. Không dùng [xxx]="signal()" với dấu gọi vì sẽ mất two-way binding.

Demo

npx nx serve core-ui

Truy cập:

  • LibsUiComponentsInputsMultiLanguageComponent: http://localhost:4500/components/inputs/multi-language
  • LibsUiComponentsInputsMultiLanguageItemsComponent: http://localhost:4500/components/inputs/multi-language-items