libphonenumber-js has no framework dependencies, so it fits Vue and Angular equally well. Here is the smallest working integration for each, using each framework's own validation tools.
Vue 3 composable
Return a computed value, so the template updates as the input changes.
import { computed } from 'vue'
import { parsePhoneNumberFromString } from 'libphonenumber-js'
export function usePhone(input, country = 'US') {
const phone = computed(() => parsePhoneNumberFromString(input.value || '', country))
const isValid = computed(() => !!phone.value && phone.value.isValid())
const e164 = computed(() => (phone.value ? phone.value.number : ''))
return { isValid, e164 }
}Using it in a Vue component
Bind the input with v-model and show the error when a value exists but is not valid.
<script setup>
import { ref } from 'vue'
import { usePhone } from './usePhone'
const raw = ref('')
const { isValid, e164 } = usePhone(raw)
</script>
<template>
<input v-model="raw" type="tel" />
<p v-if="raw && !isValid">Enter a valid phone number</p>
</template>Angular validator
Write a ValidatorFn once and attach it to any reactive form control.
import { AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms'
import { parsePhoneNumberFromString, CountryCode } from 'libphonenumber-js'
export function phoneValidator(country: CountryCode = 'US'): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
if (!control.value) return null
const phone = parsePhoneNumberFromString(control.value, country)
return phone && phone.isValid() ? null : { phone: true }
}
}
// this.form = this.fb.group({ phone: ['', phoneValidator('GB')] })Store the E.164 value
Whichever framework you use, save the E.164 form of the number. It is the one format that compares and de-duplicates cleanly. Try the E.164 converter to see the result for your own numbers.