CngxPhoneInput
projects/forms/input/phone-input/phone-input.component.ts
Import#
import { CngxPhoneInput } from '@cngx/forms/input'
Description#
International phone field composing a country picker with a region-aware mask.
CngxPhoneInput wires a CngxSelect (country) to a CngxInputMask
(phone:<region>): the selected country drives the mask region through a
computed(), so picking a country re-targets the mask with zero manual sync.
It provides CNGX_FORM_FIELD_CONTROL and is the single form-field
control - the inner select is shielded from the surrounding cngx-form-field
(a null CngxFormFieldPresenter in the inner element's providers, via
CngxPhoneInputDetach) so only this component wires the field ARIA and value.
The country list is consumer-overridable through [countries]. Selecting a
country pre-fills its dial code (e.g. +49); switching country clears the
entered national number (the mask's documented auto-clear on pattern change)
and re-seeds the new dial code.
<cngx-form-field [field]="f.phone">
<cngx-phone-input [(value)]="phone" />
</cngx-form-field>Metadata#
Host#
Providers#
CNGX_FORM_FIELD_CONTROL- useExisting
CngxPhoneInput
Relationships
Index#
Properties
Methods
Derived State
HostBindings
Inputs#
readonly Country[]Overrides the picker's country list.
CNGX_PHONE_COUNTRIESThe selected country. Two-way bindable; defaults to the first entry.
CNGX_PHONE_COUNTRIES[0]Accessible label for the country picker. Per-instance override; otherwise
resolves through CNGX_INPUT_CONFIG.ariaLabels.phoneCountry (EN 'Country').
''Consumer disable knob; the effective disabled also folds in the field.
false, { alias: 'disabled' }Reason announced via aria-describedby while the control is disabled.
''"auto" | "landline" | "mobile"Which mask alternate to use. 'auto' (default) picks landline vs mobile by
length; 'mobile'/'landline' force that grouping immediately, with no
length threshold to cross. Two-way bindable for a manual switch.
'auto'Outputs#
"auto" | "landline" | "mobile"Which mask alternate to use. 'auto' (default) picks landline vs mobile by
length; 'mobile'/'landline' force that grouping immediately, with no
length threshold to cross. Two-way bindable for a manual switch.
Instance Properties#
Methods#
HostBindings#
| Binding | Expression |
|---|---|
[attr.aria-labelledby] | labelledBy() |
[attr.aria-label] | ariaLabelAttr() |
[attr.aria-disabled] | ariaDisabled() |
[class.cngx-phone-input--disabled] | disabled() |
[class.cngx-phone-input--focused] | focused() |
HostListeners#
| Event | Handler |
|---|---|
(focusin) | focusin() |
(focusout) | focusout() |
libphonenumber-js adapter
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import { form, schema, required } from '@angular/forms/signals';
import { parsePhoneNumberFromString, type CountryCode } from 'libphonenumber-js';
import { CngxFormField, CngxLabel, CngxHint } from '@cngx/forms/field';
import {
CngxPhoneInput,
providePhoneMetadata,
type CngxPhoneMetadata,
type Country,
} from '@cngx/forms/input';
/**
* CngxPhoneInput driven by a real libphonenumber-js line-type adapter.
*
* The metadata-adapter demo story ships a toy regex stub; this playground
* wires the production library instead. The adapter receives the national
* subscriber digits (CngxPhoneInput strips the dial code) and the region,
* parses them with libphonenumber-js, and maps the parsed number type onto
* the strategy contract. As you type, the mask switches to the mobile
* grouping the moment libphonenumber can classify the prefix - the typed
* digits stay put across the regroup.
*
* libphonenumber-js lives in the playground dependencies only; cngx never
* adds it to its own graph - the adapter is consumer-authored by design.
*/
const libphonenumberAdapter: CngxPhoneMetadata = {
lineType: (region, national) => {
if (!national) {
return 'unknown';
}
const parsed = parsePhoneNumberFromString(national, region as CountryCode);
switch (parsed?.getType()) {
case 'MOBILE':
return 'mobile';
case 'FIXED_LINE':
return 'fixedLine';
default:
// Ambiguous (FIXED_LINE_OR_MOBILE) or not yet classifiable: keep the
// length-based fallback rather than guessing.
return 'unknown';
}
},
};
@Component({
selector: 'app-root',
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [CngxFormField, CngxLabel, CngxHint, CngxPhoneInput],
viewProviders: [providePhoneMetadata(libphonenumberAdapter)],
template: `
<div style="max-width: 22rem; display: grid; gap: 0.75rem">
<p style="margin: 0; opacity: 0.8; font-size: 0.875rem">
libphonenumber-js classifies the number as you type. Pick Germany and
type a number starting 15/16/17, or pick the UK and type one starting
7 - the grouping switches to mobile and your digits are preserved.
</p>
<cngx-form-field [field]="phoneField">
<label cngxLabel>Phone number</label>
<cngx-phone-input [countries]="countries" [(country)]="country" />
<span cngxHint>Line type comes from libphonenumber-js, not a fixed mask</span>
</cngx-form-field>
<span style="font-size: 0.8125rem; opacity: 0.7">
Country: {{ country().label }} · Value: {{ phoneField().value() || '(empty)' }}
</span>
</div>
`,
})
export class LibphonenumberExample {
protected readonly countries: Country[] = [
{ region: 'DE', dialCode: '+49', label: 'Germany' },
{ region: 'AT', dialCode: '+43', label: 'Austria' },
{ region: 'GB', dialCode: '+44', label: 'United Kingdom' },
{ region: 'US', dialCode: '+1', label: 'United States' },
];
protected readonly country = signal<Country>(this.countries[0]);
private readonly phoneModel = signal({ phone: '' });
private readonly phoneSchema = schema<{ phone: string }>((root) => {
required(root.phone);
});
protected readonly phoneForm = form(this.phoneModel, this.phoneSchema);
protected readonly phoneField = this.phoneForm.phone;
}
export { LibphonenumberExample as AppComponent };