I created this component called country-flag-dropdown.ts and html Component:
import {Component, OnInit, Input, Output, EventEmitter} from "@angular/core";
import {FormGroup, FormControl} from "@angular/forms";
import {CountryDataService} from "projects/pitxpress-admin-app/src/app/pitxpress/api-services/country/country-data.service";
import {ICountry} from "projects/pitxpress-admin-app/src/app/pitxpress/api-services/country/models/country.interface";
import {PhoneNumberUtil, PhoneNumberFormat} from "google-libphonenumber";
/**
* Country flag dropdown component.
*/
@Component({
selector: "app-country-flag-dropdown",
templateUrl: "./country-flag-dropdown.component.html",
styleUrls: ["./country-flag-dropdown.component.scss"],
})
export class CountryFlagDropdownComponent implements OnInit {
/**
* Form group for the component.
*/
@Input() public form!: FormGroup;
/**
* Control name for the phone number input.
*/
@Input() public controlName!: string;
/**
* Event emitter for phone number changes.
*/
@Output() public phoneNumberChange = new EventEmitter<string>();
/**
* List of countries.
*/
public countries: ICountry[] = [];
/**
* Selected dial code.
*/
public selectedDialCode: string = "";
/**
* Selected country.
*/
public selectedCountry: ICountry | undefined;
/**
* Country control form control.
*/
public countryControl = new FormControl();
/**
* Phone number utility instance.
*/
private phoneNumberUtil = PhoneNumberUtil.getInstance();
/**
* Form group for country control.
*/
public countryForm = new FormGroup({
countryControl: new FormControl(),
});
/**
* Constructor.
* @param countryDataService Country data service instance.
*/
constructor(private countryDataService: CountryDataService) {}
/**
* Initialize component.
*/
public ngOnInit(): void {
// Initialize countries data and set initial country to United States if it exists
this.countryDataService.getCountries().subscribe((countries) => {
this.countries = countries;
this.initInitialCountry();
this.checkExistingPhoneNumber();
});
// Subscribe to country control value changes
this.countryControl.valueChanges.subscribe((value) => {
this.onCountryChange(value);
});
}
/**
* Initialize initial country to United States if it exists.
*/
private initInitialCountry(): void {
const usa = this.countries.find((country) => country.isoCode2 === "US");
if (usa) {
this.selectedCountry = usa;
this.selectedDialCode = usa.dialCodes[0];
this.countryControl.setValue(this.selectedDialCode, {emitEvent: false});
this.form.get(this.controlName)?.setValue(this.selectedDialCode);
}
}
/**
* Check if there's an existing phone number.
*/
private checkExistingPhoneNumber(): void {
const phoneNumber = this.form.get(this.controlName)?.value;
if (phoneNumber) {
this.selectedDialCode = this.extractDialCode(phoneNumber);
this.form.get(this.controlName)?.setValue(phoneNumber);
const iso2Code = this.getIso2CodeFromPhoneNumber(phoneNumber);
if (iso2Code) {
this.updateCountrySelectionByIsoCode(iso2Code);
}
}
}
/**
* Handle country change event.
* @param dialCode Selected dial code.
*/
public onCountryChange(dialCode: string): void {
const selectedCountry = this.countries.find((country) => country.dialCodes.includes(dialCode));
if (selectedCountry) {
this.selectedDialCode = dialCode;
this.selectedCountry = selectedCountry;
const inputValue = this.form.get(this.controlName)?.value.replace(this.selectedDialCode, "");
this.form.get(this.controlName)?.setValue(this.selectedDialCode + inputValue);
this.phoneNumberChange.emit(this.selectedDialCode + inputValue);
} else {
// If the country is selected from the dropdown, update the selectedCountry and selectedDialCode
const selectedCountry = this.countries.find((country) => country.dialCodes[0] === dialCode);
if (selectedCountry) {
this.selectedCountry = selectedCountry;
this.selectedDialCode = dialCode;
}
}
}
/**
* Handle phone number input event.
* @param event Input event.
*/
public onPhoneNumberInput(event: any): void {
const inputElement = event.target as HTMLInputElement;
const inputValue = inputElement.value.trim();
this.formatPhoneNumber(inputValue);
const countryCode = this.getCountryCodeFromPhoneNumber(inputValue);
if (countryCode) {
const isoCode = this.phoneNumberUtil.getRegionCodeForCountryCode(parseInt(countryCode));
if (isoCode) {
this.updateCountrySelectionByIsoCode(isoCode);
}
}
// Update the phone number value in the form
this.form.get(this.controlName)?.setValue(inputValue);
// Emit the phone number change event
this.phoneNumberChange.emit(inputValue);
}
/**
* Extract dial code from phone number.
* @param phoneNumber Phone number.
* @returns Dial code.
*/
private extractDialCode(phoneNumber: string): string {
for (const country of this.countries) {
if (Array.isArray(country.dialCodes)) {
for (const dialCode of country.dialCodes) {
if (phoneNumber.startsWith(dialCode)) {
return dialCode;
}
}
}
}
return "";
}
/**
* Update country selection by ISO code.
* @param isoCode ISO code.
*/
private updateCountrySelectionByIsoCode(isoCode: string): void {
const selectedCountry = this.countries.find((country) => country.slug === isoCode);
if (selectedCountry) {
console.log("Updating country selection by ISO code:", selectedCountry);
this.selectedCountry = selectedCountry;
this.selectedDialCode = selectedCountry.dialCodes[0];
this.countryControl.setValue(this.selectedDialCode, {emitEvent: false});
}
}
/**
* Get ISO 2 code from phone number.
* @param phoneNumber Phone number.
* @returns ISO 2 code.
*/
private getIso2CodeFromPhoneNumber(phoneNumber: string): string {
try {
const number = this.phoneNumberUtil.parse(phoneNumber, ""); // Use the default region for parsing
return this.phoneNumberUtil.getRegionCodeForNumber(number) || "";
} catch (error) {
console.error("Error parsing phone number:", error);
return "";
}
}
/**
* Get country code from phone number.
* @param phoneNumber Phone number.
* @returns Country code.
*/
private getCountryCodeFromPhoneNumber(phoneNumber: string): string {
try {
const number = this.phoneNumberUtil.parse(phoneNumber, "");
return number?.getCountryCode()?.toString() || "";
} catch (error) {
console.error("Error parsing phone number:", error);
return "";
}
}
/**
* Format phone number.
* @param phoneNumber Phone number.
* @returns Formatted phone number.
*/
public formatPhoneNumber(phoneNumber: string): string {
try {
const number = this.phoneNumberUtil.parse(phoneNumber, this.selectedCountry?.isoCode2);
return this.phoneNumberUtil.format(number, PhoneNumberFormat.INTERNATIONAL);
} catch (error) {
console.error("Error formatting phone number:", error);
return phoneNumber;
}
}
}
HTML:
<div class="input-group country-dropdown-group" [formGroup]="form">
<form [formGroup]="countryForm">
<p-dropdown
[options]="countries"
optionLabel="name"
optionValue="dialCodes[0]"
appendTo="body"
[filter]="true"
filterBy="name"
formControlName="countryControl"
class="p-inputgroup-addon country-flag-selector">
<ng-template let-country pTemplate="selectedItem">
<div class="country-selector-item">
<img [src]="'assets/flags/' + selectedCountry?.slug + '.svg'" class="country-flag" />
<span>{{ selectedCountry?.slug }}</span>
</div>
</ng-template>
<ng-template let-country pTemplate="item">
<div class="country-selector-item">
<img [src]="'assets/flags/' + country.slug + '.svg'" class="country-flag" />
<span>{{ country.name }}</span>
</div>
</ng-template>
</p-dropdown>
</form>
<input
type="text"
pInputText
[formControlName]="controlName"
maxlength="15"
placeholder="Phone Number"
class="form-control"
(input)="onPhoneNumberInput($event)"
[value]="formatPhoneNumber(form.get(controlName)?.value)"/>
</div>
My goal is creating a country flag dropdown where the user can select flag or type in a phone number and the flag will show. But with this current code I'm not able to pick a flag from the dropdown and it won't reflect in the UI.
I think I have a general idea on where it's breaking. I have an @Input Form but I also have a CountyForm. Both forms seem to be being accessed in different places which is just getting a bit confusing. The idea of the @Input form - is this to try to add to an existing form on the parent component. In this line <img [src]="'assets/flags/' + selectedCountry?.slug + '.svg'" class="country-flag" />
if I change the selectedCountry?.slug to country, I'm able to pick the flag from the dropdown and it will relflect on the UI but then I'm not able to detect a flag when I type in a number so its taking away one fuctionality when I need both. I'm just curious as to how I can accomplish this?
You can try adding in your CountryFlagDropdownComponent OnInit this subscription:
this.form.get(this.controlName)?.valueChanges.subscribe((value) => {
this.onPhoneNumberInput(value);
});
The onPhoneNumberInput method takes a phoneNumber as input and updates the selected country and onCountryChange method updates the phone number based on selected country from the dropdown.
public onPhoneNumberInput(phoneNumber: string): void {
this.formatPhoneNumber(phoneNumber);
const countryCode = this.getCountryCodeFromPhoneNumber(phoneNumber);
if (countryCode) {
const isoCode = this.phoneNumberUtil.getRegionCodeForCountryCode(parseInt(countryCode));
if (isoCode) {
this.updateCountrySelectionByIsoCode(isoCode);
}
}
this.form.get(this.controlName)?.setValue(phoneNumber);
this.phoneNumberChange.emit(phoneNumber);
}
public onCountryChange(dialCode: string): void {
const selectedCountry = this.countries.find((country) => country.dialCodes.includes(dialCode));
if (selectedCountry) {
this.selectedDialCode = dialCode;
this.selectedCountry = selectedCountry;
const inputValue = this.form.get(this.controlName)?.value.replace(this.selectedDialCode, "");
this.form.get(this.controlName)?.setValue(this.selectedDialCode + inputValue);
this.phoneNumberChange.emit(this.selectedDialCode + inputValue);
}
}
At the end you should update the html with (input)="onPhoneNumberInput($event.target.value)"