Angular international telephone input (intl-tel-input UI + libphonenumber-js validation). ControlValueAccessor. SSR-safe.
npm install ngxsmk-tel-inputintl-tel-input for the UI and libphonenumber-js for parsing/validation. Implements ControlValueAccessor so it plugs into Angular Forms.
+14155550123). SSR‑safe via lazy browser‑only import.
nationalMode)
window on the server)
peerDependencies target Angular >=17. Fully compatible with Angular 17, 18, 19, 20, 21, and future versions.
bash
npm i ngxsmk-tel-input intl-tel-input libphonenumber-js
`
$3
Update your app’s angular.json:
`jsonc
{
"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
`css
.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/flags@2x.png"); }
}
`
Restart the dev server after changes.
---
🚀 Quick start (Reactive Forms)
`ts
// 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:
})
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
`html
`
---
🈺 Localization & RTL
You can localize the dropdown/search labels and override country names.
Korean example
`ts
[initialCountry]="'KR'"
[preferredCountries]="['KR','US','JP']"
[i18n]="koLabels"
[localizedCountries]="koCountries">
// in component
koLabels = {
selectedCountryAriaLabel: '선택한 국가',
countryListAriaLabel: '국가 목록',
searchPlaceholder: '국가 검색',
zeroSearchResults: '결과 없음',
noCountrySelected: '선택된 국가 없음'
};
koCountries = {
KR: '대한민국',
US: '미국',
JP: '일본',
CN: '중국'
};
`
Arabic + RTL example
`ts
dir="rtl"
label="الهاتف"
hint="اكتب رمز المنطقة"
[initialCountry]="'AE'"
[preferredCountries]="['AE','SA','EG']"
[i18n]="arLabels"
[localizedCountries]="arCountries"
[dropdownAttachToBody]="false"
>
`
⚙️ API
$3
| 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 (avoids clipping/overflow). |
| dropdownZIndex | number | 2000 | Z‑index for dropdown panel. |
| i18n | IntlTelI18n | — | Localize dropdown/search/ARIA labels. |
| localizedCountries | Partial | — | 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). |
| theme | 'light' \| 'dark' \| 'auto' | 'auto' | Theme preference for the component. |
> CountryCode is the ISO‑2 uppercase code from libphonenumber-js (e.g. US, GB).
$3
| 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. |
$3
* focus(): void
* selectCountry(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
$3
Override on the element or a parent container:
`html
`
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
$3
The component supports light, dark, and auto themes:
`ts
import { NgxsmkTelInputComponent, ThemeService } from 'ngxsmk-tel-input';
// Component-level theme
// Global theme management
@Component({})
export class MyComponent {
private themeService = inject(ThemeService);
setDarkTheme() {
this.themeService.setTheme('dark');
}
// Subscribe to theme changes
theme$ = this.themeService.currentTheme$;
}
`
Themes:
- 'light': Light theme
- 'dark': Dark theme
- 'auto': Automatically follows system preference (default)
Dark mode: wrap in a .dark parent or use [theme]="'dark'" — tokens adapt automatically.
---
✔️ Validation patterns
`html
Phone is required
Please enter a valid phone number
Invalid country code
`
$3
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 store raw/national yourself, or adapt the emitter to output national.
---
🌐 SSR notes
* The library lazy‑imports intl-tel-input only in the browser (guards with isPlatformBrowser).
* No window/document usage on the server path.
---
🧪 Local development
This repo is an Angular workspace with a library.
`bash
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-.tgz
`
> Workspace aliasing via tsconfig.paths also 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. Ensure your app uses Angular 17 or higher.
Vite/Angular “Failed to resolve import …”
Clear .angular/cache, rebuild the lib, and restart ng serve.
---
📃 License
MIT
🙌 Credits
* UI powered by intl-tel-input
* Parsing & validation by libphonenumber-js`