ngxsmk-tel-input
v1.6.8
Published
Angular international telephone input (intl-tel-input UI + libphonenumber-js validation). ControlValueAccessor. SSR-safe.
Maintainers
Readme
ngxsmk-tel-input
An Angular telephone input component with country dropdown, flags, and robust validation/formatting.
Wraps intl-tel-input for the UI and libphonenumber-js for parsing/validation. Implements ControlValueAccessor so it plugs into Angular Forms.
Emits E.164 by default (e.g.
+14155550123). SSR‑safe via lazy browser‑only import.
🚀 Try it live on StackBlitz
✨ Features
- Country dropdown with flags
- E.164 output (display can be national with
nationalMode) - Reactive & template‑driven Forms support (CVA)
- Built‑in validation using libphonenumber‑js
- Enhanced validation: Detects invalid country codes (like "11", "99") and shows appropriate error states
- SSR‑friendly (no
windowon the server) - Easy theming via CSS variables
- Nice UX options: label/hint/error text, sizes, variants, clear button, autofocus, select-on-focus
- New: Masking & caret-friendly as-you-type formatting (optional)
- New: Format only when valid (formatWhenValid) and lock once valid (lockWhenValid) to prevent extra digits
✅ Requirements
- Angular 17 – 19
- Node 18 or 20
Library
peerDependenciestarget Angular>=17 <20. Your app can be 17, 18, or 19.
📦 Install
npm i ngxsmk-tel-input intl-tel-input libphonenumber-jsAdd styles & flag assets (in your app, not the library)
Update your app’s angular.json:
{
"projects": {
"your-app": {
"architect": {
"build": {
"options": {
"styles": [
"node_modules/intl-tel-input/build/css/intlTelInput.css"
],
"assets": [
{ "glob": "**/*", "input": "node_modules/intl-tel-input/build/img", "output": "assets/intl-tel-input/img" }
]
}
}
}
}
}
}Optional override to ensure flags resolve (e.g., Vite/Angular 17+): add to your global styles
.iti__flag { background-image: url("/assets/intl-tel-input/img/flags.png"); }
@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) {
.iti__flag { background-image: url("/assets/intl-tel-input/img/[email protected]"); }
}Restart the dev server after changes.
🚀 Quick start (Reactive Forms)
// app.component.ts
import { Component, inject } from '@angular/core';
import { ReactiveFormsModule, FormBuilder, Validators } from '@angular/forms';
import { JsonPipe } from '@angular/common';
import { NgxsmkTelInputComponent, IntlTelI18n, CountryMap } from 'ngxsmk-tel-input';
@Component({
selector: 'app-root',
standalone: true,
imports: [ReactiveFormsModule, NgxsmkTelInputComponent, JsonPipe],
template: `
<form [formGroup]="fg" style="max-width:420px;display:grid;gap:12px">
<ngxsmk-tel-input
formControlName="phone"
label="Phone"
hint="Include area code"
dir="ltr"
[initialCountry]="'US'"
[preferredCountries]="['US','GB','AU']"
[i18n]="enLabels"
[localizedCountries]="enCountries"
[separateDialCode]="true" <!-- dial code after the flag -->
[formatWhenValid]="'typing'" <!-- live mask only when valid -->
[lockWhenValid]="true" <!-- stop extra digits once valid -->
></ngxsmk-tel-input>
<pre>Value: {{ fg.value | json }}</pre>
</form>
`
})
export class AppComponent {
private readonly fb = inject(FormBuilder);
fg = this.fb.group({ phone: ['', Validators.required] });
// English UI labels (dropdown/search/ARIA)
enLabels: IntlTelI18n = {
selectedCountryAriaLabel: 'Selected country',
countryListAriaLabel: 'Country list',
searchPlaceholder: 'Search country',
zeroSearchResults: 'No results',
noCountrySelected: 'No country selected'
};
// Optional: only override the names you care about
enCountries: CountryMap = {
US: 'United States',
GB: 'United Kingdom',
AU: 'Australia',
CA: 'Canada'
};
}
Value semantics: the form control value is E.164 (e.g., +14155550123) when valid, or null when empty/invalid.
📝 Template‑driven usage
<form #f="ngForm">
<ngxsmk-tel-input name="phone" [(ngModel)]="phone"></ngxsmk-tel-input>
</form>
<!-- phone is an E.164 string or null -->🈺 Localization & RTL
You can localize the dropdown/search labels and override country names.
Korean example
<ngxsmk-tel-input
[initialCountry]="'KR'"
[preferredCountries]="['KR','US','JP']"
[i18n]="koLabels"
[localizedCountries]="koCountries">
</ngxsmk-tel-input>
// in component
koLabels = {
selectedCountryAriaLabel: '선택한 국가',
countryListAriaLabel: '국가 목록',
searchPlaceholder: '국가 검색',
zeroSearchResults: '결과 없음',
noCountrySelected: '선택된 국가 없음'
};
koCountries = {
KR: '대한민국',
US: '미국',
JP: '일본',
CN: '중국'
};
Arabic + RTL example
<ngxsmk-tel-input
dir="rtl"
label="الهاتف"
hint="اكتب رمز المنطقة"
[initialCountry]="'AE'"
[preferredCountries]="['AE','SA','EG']"
[i18n]="arLabels"
[localizedCountries]="arCountries"
[dropdownAttachToBody]="false" <!-- so popup inherits rtl from host -->
></ngxsmk-tel-input>
⚙️ API
Inputs
| Name | Type | Default | Description |
|------------------------|---------------------------------------------|-------------------------|-------------------------------------------------------------------------------|
| initialCountry | CountryCode \| 'auto' | 'US' | Starting country. 'auto' uses geoIp stub (US by default). |
| preferredCountries | CountryCode[] | ['US','GB'] | Pin these at the top. |
| onlyCountries | CountryCode[] | — | Limit selectable countries. |
| nationalMode | boolean | false | If true, display national format in the input. Value still emits E.164. |
| separateDialCode | boolean | false | Show dial code outside the input. |
| allowDropdown | boolean | true | Enable/disable dropdown. |
| placeholder | string | 'Enter phone number' | Input placeholder. |
| autocomplete | string | 'tel' | Native autocomplete. |
| disabled | boolean | false | Disable the control. |
| label | string | — | Optional floating label text. |
| hint | string | — | Helper text below the control. |
| errorText | string | — | Custom error text. |
| size | 'sm' \| 'md' \| 'lg' | 'md' | Control height/typography. |
| variant | 'outline' \| 'filled' \| 'underline' | 'outline' | Visual variant. |
| showClear | boolean | true | Show a clear (×) button when not empty. |
| autoFocus | boolean | false | Focus on init. |
| selectOnFocus | boolean | false | Select all text on focus. |
| formatOnBlur | boolean | true | Pretty‑print on blur (national if nationalMode). |
| showErrorWhenTouched | boolean | true | Show error styles only after blur. |
| dropdownAttachToBody | boolean | true | Attach dropdown to <body> (avoids clipping/overflow). |
| dropdownZIndex | number | 2000 | Z‑index for dropdown panel. |
| i18n | IntlTelI18n | — | Localize dropdown/search/ARIA labels. |
| localizedCountries | Partial<Record<CountryCode, string>> | — | Override country display names (ISO-2 keys). |
| dir | 'ltr' \| 'rtl' | 'ltr' | Text direction for the control. |
| autoPlaceholder | 'off' \| 'polite' \| 'aggressive' | 'polite' | Example placeholders. Requires utilsScript unless off. |
| utilsScript | string | — | Path/URL to utils.js (needed for example placeholders). |
| customPlaceholder | (example: string, country: any) => string | — | Transform the example placeholder. |
| clearAriaLabel | string | 'Clear phone number' | ARIA label for the clear button. |
| lockWhenValid | boolean | true | Prevent appending extra digits once the number is valid (editing/replacing still allowed). |
CountryCodeis the ISO‑2 uppercase code fromlibphonenumber-js(e.g.US,GB).
Outputs
| Event | Payload | Description |
| ---------------- | ---------------------------------------------------------- | ------------------------------------ |
| countryChange | { iso2: CountryCode } | Fired when selected country changes. |
| validityChange | boolean | Fired when validity flips. |
| inputChange | { raw: string; e164: string \| null; iso2: CountryCode } | Emitted on every keystroke. |
Public methods
focus(): voidselectCountry(iso2: CountryCode): void
🧠 Formatting & validity behavior
No formatting while invalid. As-you-type masking only starts when the digits form a valid number for the selected country.
Sri Lanka / “trunk 0”: a national format may include a leading 0 (e.g., 071…). The emitted E.164 always excludes it (+94 71…)—this is expected.
Lock when valid: with lockWhenValid enabled, once the number is valid, appending more digits is blocked (you can still delete/replace).
For rare patterns not covered by libphonenumber-js, the control falls back to raw digits (no forced mask) until it becomes valid.
🎨 Theming (CSS variables)
Override on the element or a parent container:
<ngxsmk-tel-input style="
--tel-border:#cbd5e1;
--tel-ring:#22c55e;
--tel-radius:14px;
--tel-dd-item-hover: rgba(34,197,94,.12);
--tel-dd-z: 3000;
"></ngxsmk-tel-input>Available tokens:
- Input:
--tel-bg,--tel-fg,--tel-border,--tel-border-hover,--tel-ring,--tel-placeholder,--tel-error,--tel-radius,--tel-focus-shadow - Dropdown:
--tel-dd-bg,--tel-dd-border,--tel-dd-shadow,--tel-dd-radius,--tel-dd-item-hover,--tel-dd-search-bg,--tel-dd-z
Dark mode: wrap in a .dark parent — tokens adapt automatically.
✔️ Validation patterns
<ngxsmk-tel-input formControlName="phone"></ngxsmk-tel-input>
<div class="error" *ngIf="fg.get('phone')?.hasError('required')">Phone is required</div>
<div class="error" *ngIf="fg.get('phone')?.hasError('phoneInvalid')">Please enter a valid phone number</div>
<div class="error" *ngIf="fg.get('phone')?.hasError('phoneInvalidCountryCode')">Invalid country code</div>Enhanced Validation Features
The component now includes enhanced validation that detects and handles various invalid phone number scenarios:
Invalid Country Code Detection
- Input:
"1123456789"or"99123456789" - Error:
phoneInvalidCountryCode - Reason: "11" and "99" are not valid country codes
Valid Country Code, Invalid Number
- Input:
"+9111023533"(India country code, Delhi area code, but incomplete subscriber number) - Error:
phoneInvalid - Reason: Valid country/area codes but invalid number format
Valid Numbers
- Input:
"+12025551234"(US),"+442071234567"(UK),"+91112345678"(India) - Status: Valid
- Output: E.164 format string
- When valid → control value = E.164 string
- When invalid/empty → value = null, and validator sets appropriate error type
Need national string instead of E.164? Use
(inputChange)and storeraw/nationalyourself, or adapt the emitter to output national.
🌐 SSR notes
- The library lazy‑imports
intl-tel-inputonly in the browser (guards withisPlatformBrowser). - No
window/documentusage on the server path.
🧪 Local development
This repo is an Angular workspace with a library.
# Build the library
ng build ngxsmk-tel-input
# Option A: use it inside a demo app in the same workspace
ng serve demo
# Option B: install locally via tarball in another app
cd dist/ngxsmk-tel-input && npm pack
# in your other app
npm i ../path-to-workspace/dist/ngxsmk-tel-input/ngxsmk-tel-input-<version>.tgzWorkspace aliasing via
tsconfig.pathsalso works (map"ngxsmk-tel-input": ["dist/ngxsmk-tel-input"]).
🧯 Troubleshooting
UI looks unstyled / bullets in dropdown
Add the CSS and assets in angular.json (see Install). Restart the dev server.
Flags don’t show
Ensure the assets copy exists under /assets/intl-tel-input/img and add the CSS override block above.
TS2307: Cannot find module 'ngxsmk-tel-input'
Build the library first so dist/ngxsmk-tel-input exists. If using workspace aliasing, add a paths entry to the root tsconfig.base.json.
Peer dependency conflict when installing
The lib peers are @angular/* >=17 <20. Upgrade your app or install a compatible version.
Vite/Angular “Failed to resolve import …”
Clear .angular/cache, rebuild the lib, and restart ng serve.
📃 License
🙌 Credits
- UI powered by
intl-tel-input - Parsing & validation by
libphonenumber-js
Last updated: 2025-08-29
