bs-phone-input is a phone-number field: a country picker with flags, a dial code that cannot be edited away, and as-you-type formatting. The bound value is E.164 — +32470123456 — or null while empty, so a form posts one canonical string.
Validation rules are loaded per calling code on first interaction, so a field costs about 20 kB gzip when the user reaches it and nothing before that. (phonechange) reports valid as undefined until those rules arrive — deliberately, rather than claiming a number is valid before anything has checked it.
import { Component, model } from '@angular/core';import { FormsModule } from '@angular/forms';import { BsFormComponent } from '@mintplayer/ng-bootstrap/form';import { BsPhoneInputComponent } from '@mintplayer/ng-bootstrap/phone-input';import type { PhoneChangeEventDetail } from '@mintplayer/ng-bootstrap/phone-input';@Component({ selector: 'my-phone-demo', templateUrl: './my-phone-demo.component.html', imports: [FormsModule, BsFormComponent, BsPhoneInputComponent],})export class MyPhoneDemoComponent { // The bound value is E.164 — '+32470123456' — or null while empty. readonly phone = model<string | null>(null); onPhoneChange(detail: PhoneChangeEventDetail) { // detail.valid is undefined until the country's rules have loaded. console.log(detail.country, detail.nationalNumber, detail.valid); }}
Copied!
Localized country names
Country names come from Intl.DisplayNames, so they are localized and collated in the viewer's language at no download cost. Leave locale unset to follow the browser — the default, and what most apps should ship.
Open the picker above after switching: the list re-sorts, because an alphabetical list of English names is not alphabetical to a Dutch reader.
Restricting and pinning countries
+31
null
<!-- allowed-countries, NOT only-countries: Angular refuses to bind any attribute whose name starts with 'on'. --><bs-phone-input [(ngModel)]="phone" [allowedCountries]="['be', 'nl', 'lu']" [preferredCountries]="['nl']" defaultCountry="nl" />
Copied!
Inside an input group
bs-input-group joins any mix of controls into one continuous field, including a whole phone input: its outer corners pair with the neighbouring addon and the borders collapse.
Tel
+32
<!-- bs-input-group joins any mix of controls, including a whole phone input: the corners pair and the borders collapse. Below ~6rem per control the row wraps instead of crushing them; override --mp-group-min-item-width for a denser toolbar. --><bs-input-group> <span class="addon">Tel</span> <bs-phone-input [(ngModel)]="phone" defaultCountry="be" /></bs-input-group>
Copied!
Validation
Tick Required above and blur the empty field. The message from errorMessages is rendered under the field and referenced by aria-errormessage and aria-describedby from the number input, so a screen reader reads it with the field.
// The control's errors reach the field through BsControlValidityDirective,// which the component applies itself: the message renders under the field,// referenced by aria-errormessage from the number input.<bs-phone-input [(ngModel)]="phone" name="phone" required [errorMessages]="{ required: 'A phone number is required.' }" />
Copied!
Keyboard
Tab — two stops: the country picker, then the number field.
In the picker: ↑/↓ to move, type a country name to jump to it, Enter to choose, Esc to close. Choosing a country moves focus to the number field.
In the number field: Backspace/Delete always remove a digit, never a space or bracket, so one keypress is never swallowed by the formatter.
Pasting or typing +44 … switches the country and strips the dial code.
Angular forms
Template-driven [(ngModel)] and reactive [formControl] both bind through the component's ControlValueAccessor: the value, the disabled state, and touched when focus leaves.
Template-driven
+32
Value: null
dirty: false · touched: false · disabled: false
Reactive
+32
Value: null
dirty: false · touched: false · disabled: false
<!-- template-driven (FormsModule) --><bs-phone-input [(ngModel)]="value" defaultCountry="be"></bs-phone-input><!-- reactive (ReactiveFormsModule): control = new FormControl(...) --><bs-phone-input [formControl]="control" defaultCountry="be"></bs-phone-input>