@desource/phone-mask-angular

中文文档
2.1.0发布于 2026/11/07MITTypeScriptES Module构建来源已验证近 30 天下载 1万

Signal-based Angular phone input, directive and formatting pipe with SSR support.

原始使用文档
文档目录21 节
  1. ✨ Features
  2. 📦 Installation
  3. 🚀 Quick Start
  4. Importing
  5. Component Mode
  6. 📖 Component API
  7. Inputs
  8. Outputs
  9. Exposed Methods
  10. Custom Templates
  11. 📝 Angular Forms
  12. 🧩 Directive API
  13. 🪝 Headless API
  14. 🔤 Pipe API
  15. 🌐 SSR and Zoneless Applications
  16. 🎨 Component Styling
  17. 🛠️ Core Utilities
  18. 📦 What's Included
  19. 🔗 Related Packages
  20. 📄 License
  21. 🤝 Contributing

@desource/phone-mask-angular

Angular phone input component, headless masking function, directive, and pipe with Google libphonenumber data

npm version code coverage SonarCloud Ask CodeWiki Ask Context7 Ask DeepWiki license

A standalone, signal-based phone input for Angular 19–22, powered by the shared phone-mask core.

✨ Features

  • Standalone component with signal inputs, two-way binding, and OnPush change detection.
  • SSR and hydration support, including formatted server HTML.
  • Zoneless operation: the library does not import or require zone.js.
  • Reactive and template-driven Angular Forms through ControlValueAccessor.
  • Country search, keyboard navigation, smart cursor handling, and paste formatting.
  • Light/dark themes, copy and clear actions, and validation feedback.
  • Optional GeoIP detection and localized country names.
  • Separate CSS and a tree-shakeable Angular Package Format build produced by ng-packagr.

📦 Installation

npm install @desource/phone-mask-angular
# or
yarn add @desource/phone-mask-angular
# or
pnpm add @desource/phone-mask-angular

The package supports Angular 19–22. Keep @angular/common, @angular/core, and @angular/forms on matching versions in your application.

Import component styles once in your global stylesheet:

@import '@desource/phone-mask-angular/assets/lib.css';

The directive, headless masking function, and pipe do not require these styles.

🚀 Quick Start

Importing

import { PhoneInputComponent, PhoneMaskDirective, PhonePipe, usePhoneMask } from '@desource/phone-mask-angular';

Add the component, directive, or pipe to your standalone component's imports. Call usePhoneMask() in an injection context when building a custom input.

Component Mode

import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import { PhoneInputComponent } from '@desource/phone-mask-angular';

@Component({
  selector: 'app-profile',
  imports: [PhoneInputComponent],
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <label for="mobile">Mobile number</label>
    <phone-input id="mobile" name="mobile" country="US" [(value)]="phone" />
    <p>Digits without calling code: {{ phone() }}</p>
  `
})
export class Profile {
  readonly phone = signal('2025550123');
}

value contains digits without the country calling code. Use phoneChange for the full international number:

<phone-input [(value)]="phone" (phoneChange)="savePhone($event.full)" />

Pasting a complete international number beginning with + detects its country from existing masks, replaces the whole input, and updates the country before formatting. This applies to the component, directive, and usePhoneMask(), even with country supplied or GeoIP detect disabled. Ambiguous masks use a default country, such as US for +1. Other pastes keep the current country and use normal insertion behavior.

📖 Component API

Inputs

InputTypeDefault
valuestring''
countryISO country codeUnspecified; country selection is available
detectbooleantrue
localestringEnglish on the server, browser language after rendering
theme'auto' | 'light' | 'dark''auto'
size'compact' | 'normal' | 'large''normal'
id, namestringUnspecified; applied to the native input
disabled, readonlybooleanfalse
showCopybooleantrue
showClearbooleanfalse
withValiditybooleantrue
searchPlaceholderstring'Search country or code...'
noResultsTextstring'No countries found'
clearButtonLabelstring'Clear phone number'
dropdownClassstring''
disableDefaultStylesbooleanfalse

Setting country locks the country selector. Omit it to let users choose a country; use selectCountry() to set an initial selection without locking it.

Outputs

OutputValue
valueChangeDigits without the calling code, for [(value)]
phoneChange{ digits, full, fullFormatted }
countryChangeCountry metadata (PMaskFull)
validationChangeWhether all required digits are present
phoneFocus, phoneBlurNative FocusEvent
touchedChangetrue on blur, for Signal Forms compatibility
copiedCopied phone number
clearedNo payload

Exposed Methods

Access the component through an Angular template reference or viewChild:

<phone-input #phoneInput [(value)]="phone" />
<button type="button" (click)="phoneInput.focus()">Focus</button>
<button type="button" (click)="phoneInput.selectCountry('GB')">Use UK</button>
<button type="button" (click)="phoneInput.clear()">Clear</button>

Also available: blur(), getDigits(), getFullNumber(), getFullFormattedNumber(), isComplete(), and isValid(). Completeness checks digit count against the selected mask; it does not verify that a number is assigned or reachable.

Custom Templates

Pass an Angular TemplateRef through flagTemplate, copyTemplate, clearTemplate, or actionsTemplate. The flag template receives the country as $implicit and country; the copy template receives the copied state as $implicit and copied.

<ng-template #flag let-country>{{ country.id }}</ng-template>
<ng-template #actions><button class="pi-btn" type="button" (click)="openHelp()">Help</button></ng-template>
<phone-input [(value)]="phone" [flagTemplate]="flag" [actionsTemplate]="actions" />

actionsTemplate appears before the built-in buttons and reserves one action's width. Override --pi-actions-count if your template needs more space.

📝 Angular Forms

Import ReactiveFormsModule alongside the component:

import { ChangeDetectionStrategy, Component } from '@angular/core';
import { FormControl, ReactiveFormsModule } from '@angular/forms';
import { PhoneInputComponent } from '@desource/phone-mask-angular';

@Component({
  selector: 'app-contact-form',
  imports: [ReactiveFormsModule, PhoneInputComponent],
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `<phone-input country="US" [formControl]="phone" />`
})
export class ContactForm {
  readonly phone = new FormControl('', { nonNullable: true });
}

Form values contain digits without the country calling code. The component propagates changes, touched state, and disabled state. Add your application's validators to the control; visual completeness feedback does not register a form validator.

Supply digits valid for the selected country, excluding the calling code. Programmatic values are formatted and clamped for display without rewriting your form model; use the phoneChange event's digits field to observe the canonical value.

Template-driven forms also work with FormsModule and [(ngModel)]. Choose a form binding or [(value)] for each input so one owner controls the value.

Signal Forms also work through the component's existing bindings: Angular 21.0 uses Field / [field]; Angular 22 uses FormField / [formField]. Use the API available in your installed Angular version. The touchedChange output preserves blur handling for Angular 21's integration.

🧩 Directive API

Use a native input with your own markup and styles. Import PhoneMaskDirective into the component's imports:

<input [phoneMask]="{ country: 'US' }" [(ngModel)]="phone" />

For a fixed country, phoneMask="US" is equivalent. The directive integrates with Angular Forms and emits digits without the calling code through its value accessor. Its options accept country, locale, detect, onChange, and onCountryChange. Detection defaults to false for the directive.

<input [phoneMask]="{ country: selectedCountry(), onChange: onPhoneChange }" />

onChange receives { digits, full, fullFormatted }; onCountryChange receives country metadata.

🪝 Headless API

usePhoneMask() is a headless masking function that exposes a signal-based API for custom inputs. Call it in an Angular injection context, such as a component field initializer. State and cleanup belong to that component instance.

import { ChangeDetectionStrategy, Component, ElementRef, signal, viewChild } from '@angular/core';
import { usePhoneMask } from '@desource/phone-mask-angular';

@Component({
  selector: 'app-custom-phone',
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <input
      #input
      type="tel"
      [value]="mask.displayValue()"
      [placeholder]="mask.displayPlaceholder()"
      aria-label="Phone number"
    />
    <p>{{ mask.fullFormatted() }}</p>
    <button type="button" (click)="mask.setCountry('GB')">Use UK</button>
  `
})
export class CustomPhone {
  readonly input = viewChild<ElementRef<HTMLInputElement>>('input');
  readonly digits = signal('');
  readonly mask = usePhoneMask({
    input: () => this.input()?.nativeElement,
    value: this.digits,
    onChange: (digits) => this.digits.set(digits),
    country: 'US'
  });
}

Bind displayValue and displayPlaceholder in the template to render the custom input's value during SSR. The function attaches editing behavior after browser rendering and removes listeners when the input or component is destroyed.

value and onChange are required. Pass an input getter to attach editing behavior to a native input; omit it when only consuming the formatted signals. Options country, locale, and detect accept a plain value, signal, or getter. Detection defaults to false. Optional callbacks are onPhoneChange and onCountryChange.

The return value exposes signals for digits, displayValue, displayPlaceholder, full, fullFormatted, country, locale, formatter, isComplete, isEmpty, and shouldShowWarn, and the setCountry(code) / clear() methods.

🔤 Pipe API

Import PhonePipe into your component's imports to display a phone number. International values beginning with + can detect the country from their mask and length. Pass an ISO country code for numbers without a calling code or to resolve countries sharing a calling code:

{{ '+12345678888' | phone }}
<!-- +1 234 567 8888 -->
{{ '+12345678888' | phone: 'US': { code: false } }}
<!-- 234 567 8888 -->
{{ '+12345678888' | phone: 'US': { flag: true } }}
<!-- 🇺🇸 +1 234 567 8888 -->
{{ '+12345678888' | phone: 'US': { code: false, flag: true } }}
<!-- 🇺🇸 234 567 8888 -->

Digits without a calling code ('2345678888') and preformatted values ('234 567 8888' or '+1 234 567 8888') produce the same output. The pipe uses the selected country's mask groups, separated by spaces.

The optional second argument accepts the exported PhonePipeOptions type:

OptionTypeDefaultEffect
codebooleantrueInclude the country calling code.
flagbooleanfalsePrepend the country flag emoji.

Only an explicit + calling code is removed from the input; bare digits are treated as a number without a calling code. Without a country, numbers lacking a calling code and unsuccessful detection return the original value. Mask detection uses a default country for ties, such as US for +1; it does not inspect area-code allocations. Pass undefined or null as the country to combine detection with options.

A supplied country takes priority when its calling code matches the value, so CA keeps the Canadian flag for +1. If the calling code differs, successful detection takes priority: '+7 123 456 7788' | phone: 'US' displays +7 123 456 7788 using KZ. If detection fails, the original value is preserved.

The pipe preserves significant leading zeros and does not infer trunk prefixes or extensions. Empty, whitespace-only, null, and undefined values render as ''. Unsupported countries, invalid characters, extensions, and numbers exceeding the selected mask's length return the original value unchanged.

The pipe is pure and works during SSR without browser APIs. Angular formats again when the value, country, or options reference changes. Inline options work as shown above; replace a stored options object when changing its properties.

🌐 SSR and Zoneless Applications

Use your application's normal Angular SSR and hydration setup. The library does not require ngSkipHydration, browser-only wrappers, or Zone.js.

Server rendering uses deterministic defaults: English country names and a light theme when theme="auto". After browser rendering, the component applies the browser's locale and color preference. GeoIP requests run only in the browser. Set country, locale, and an explicit theme when the server already knows those preferences and you want the same initial appearance.

Angular 21+ uses zoneless change detection by default. Earlier versions need the matching bootstrap provider: provideExperimentalZonelessChangeDetection() on Angular 19 and early Angular 20, or provideZonelessChangeDetection() where available. See Angular's zoneless guide and hydration guide.

🎨 Component Styling

Styles are opt-in through assets/lib.css. Set CSS custom properties on .desource-phone-input and .desource-phone-dropdown:

.desource-phone-input,
.desource-phone-dropdown {
  --pi-radius: 6px;
  --pi-border-focus: #2563eb;
  --pi-font-size: 16px;
}

The dropdown is attached to document.body to avoid clipping by parent containers. Apply dropdown overrides globally or use dropdownClass; component-scoped CSS does not reach the portal.

🛠️ Core Utilities

The secondary entry point re-exports the shared core utilities and country data:

import { getFlagEmoji, formatDigitsWithMap } from '@desource/phone-mask-angular/core';

📦 What's Included

The published package contains unminified FESM2022 modules, partial Angular declarations, TypeScript declarations, and separate CSS. Angular's application build links and optimizes the library. ESM and Angular's linker are required; no UMD or CommonJS build is provided.

@desource/phone-mask-angular/
├── phone-mask-angular.css                     # Component styles, exported as assets/lib.css
├── core/index.d.ts                            # Core utilities and country data types
├── fesm2022/
│   ├── desource-phone-mask-angular.mjs         # Component, headless API, directive, and pipe
│   └── desource-phone-mask-angular-core.mjs    # Core secondary entry point
├── index.d.ts                                 # Public API declarations
├── README.md
└── package.json

📄 License

MIT © 2026 DeSource Labs

🤝 Contributing

See Contributing Guide for development, testing, and publication checks.


Made with ❤️ by DeSource Labs

版本与发布记录

@desource/phone-mask-angular 共发布 3 个版本,首次发布于 2026/10/31;过去 12 个月发布了 3 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
2.1.0latest2026/11/074 天前
2.0.02026/10/3111 天前
0.0.0-stage2026/10/3111 天前

关于 @desource/phone-mask-angular 的常见问题

@desource/phone-mask-angular 是什么?

Signal-based Angular phone input, directive and formatting pipe with SSR support.

如何安装 @desource/phone-mask-angular?

在项目目录运行 npm install @desource/phone-mask-angular,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:@angular/core(^19.0.0 || ^20.0.0 || ^21.0.0 || ^22.0.0)、@angular/forms(^19.0.0 || ^20.0.0 || ^21.0.0 || ^22.0.0)、@angular/common(^19.0.0 || ^20.0.0 || ^21.0.0 || ^22.0.0)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@desource/phone-mask-angular 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@desource/phone-mask-angular 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@desource/phone-mask-angular 可以商用吗?

@desource/phone-mask-angular 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@desource/phone-mask-angular 的下载量有多少?

根据 npm 官方下载统计,@desource/phone-mask-angular 在 2026/10/12 – 2026/11/10 这 30 天内被下载 10,223 次,日均约 341 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@desource/phone-mask-angular 还在维护吗?

最新版本 2.1.0 发布于 2026/11/07(4 天前);至今共发布 3 个版本,其中过去 12 个月发布了 3 个;首次发布于 2026/10/31。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。