I’ve shipped a small, focused UI component for modern Angular apps: an international telephone input with country flags, dropdown, and real validation/formatting.
It wraps the excellent intl-tel-input for UI and libphonenumber-js for validation/parsing, and exposes a clean Angular API that works with Reactive & Template-driven Forms (CVA). It emits E.164 numbers by default (e.g. +14155550123), and is SSR-safe.
TL;DR
✅ UI via intl-tel-input, rules via libphonenumber-js
✅ Emits clean E.164 values
✅ Works with Reactive & Template forms (ControlValueAccessor)
✅ Angular 17–19, standalone, SSR-friendly
✅ Options: separateDialCode, nationalMode, preferredCountries, attach dropdown to
, sizes, variants, clear button, autofocus, select-on-focus🌍 Localization & RTL: customize labels, country names, and direction
🔒 Optional digits-only input mode (with single leading +)
NPM: https://www.npmjs.com/package/ngxsmk-tel-input
GitHub: https://github.com/toozuuu/ngxsmk-tel-input
Install
npm i ngxsmk-tel-input intl-tel-input libphonenumber-js
Add styles & flag assets in your app (not the library). Update 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 (helps some setups resolve flags):
/* 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/flags@2x.png"); }
}
Restart your dev server after editing angular.json.
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 } 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"
[initialCountry]="'US'"
[preferredCountries]="['US','GB','AU']">
</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] });
}
Value semantics: The control emits E.164 (e.g., +14155550123) when valid, or null when empty/invalid.
Localization & RTL (i18n)
Customize dropdown/search labels and country names. RTL is as easy as dir="rtl".
import { Component, inject } from '@angular/core';
import { ReactiveFormsModule, FormBuilder, Validators } from '@angular/forms';
import { NgxsmkTelInputComponent, IntlTelI18n, CountryMap } from 'ngxsmk-tel-input';
@Component({
selector: 'app-root',
standalone: true,
imports: [ReactiveFormsModule, NgxsmkTelInputComponent],
template: `
<form [formGroup]="fg" style="max-width:420px;display:grid;gap:12px">
<ngxsmk-tel-input
formControlName="phone"
label="전화번호"
hint="지역 번호를 포함하세요"
placeholder="전화번호 입력"
dir="ltr"
[initialCountry]="'KR'"
[preferredCountries]="['KR','US','JP']"
[i18n]="koLabels"
[localizedCountries]="koCountries">
</ngxsmk-tel-input>
<pre>Value: {{ fg.value | json }}</pre>
</form>
`
})
export class AppComponent {
private fb = inject(FormBuilder);
fg = this.fb.group({ phone: ['', Validators.required] });
// Korean UI labels (dropdown/search/ARIA)
koLabels: IntlTelI18n = {
selectedCountryAriaLabel: '선택한 국가',
countryListAriaLabel: '국가 목록',
searchPlaceholder: '국가 검색',
zeroSearchResults: '결과 없음',
noCountrySelected: '선택된 국가 없음'
};
// Korean country names (override what you need)
koCountries: CountryMap = {
KR: '대한민국',
US: '미국',
JP: '일본',
CN: '중국',
GB: '영국',
DE: '독일',
FR: '프랑스',
AU: '호주',
CA: '캐나다',
IN: '인도'
};
}
Prefer Korean? Swap labels/countries to ko-KR equivalents and set dir="ltr".
Optional: Digits-Only Input
You can forbid non-numeric characters (optionally allowing a single leading +):
<ngxsmk-tel-input
formControlName="phone"
[digitsOnly]="true"
[allowLeadingPlus]="true">
</ngxsmk-tel-input>
This filters typing/paste (and keeps programmatic updates clean), while validation still uses libphonenumber-js.
Theming (CSS Variables)
<ngxsmk-tel-input style="
--tel-border:#cbd5e1;
--tel-ring:#22c55e;
--tel-radius:14px;
--tel-dd-item-hover: rgba(34,197,94,.12);
"></ngxsmk-tel-input>
Dark mode? Wrap a parent in .dark — tokens adapt automatically.
SSR Notes
The component lazy-loads intl-tel-input only in the browser (guarded with isPlatformBrowser), so no window access on the server.
Troubleshooting
- Unstyled dropdown / missing flags → ensure CSS & assets are added in angular.json, then restart dev server.
- Flags don’t show → add the CSS override block shown above to point to /assets/intl-tel-input/img.
- Peer dependency conflict → the library peers target @angular/* >=17 <20.
- Can’t import the package in a workspace → build the lib (ng build ngxsmk-tel-input) or use a tarball install.
Roadmap & Contributions
- Accessibility polish & docs samples
- More presets & examples (StackBlitz)
- Community requests!
PRs, issues, and ideas are super welcome 🙌
NPM: https://www.npmjs.com/package/ngxsmk-tel-input
GitHub: https://github.com/toozuuu/ngxsmk-tel-input
If you try it, I’d love your feedback. Happy building! 💚


Top comments (0)
Some comments may only be visible to logged-in visitors. Sign in to view all comments.