In some online forms, you might need users to enter a phone number. Wouldn't it be great if your Angular application formatted that phone number on the fly?

And wouldn't it be even better if your app formatted the phone number based on that phone number's country?

For example, users would see this format when entering a phone number for the U.S.: (919) 555-1212

And they'd see this format when entering a phone number for Brazil: (91) 95444-555

I'll show you how to do that in this guide. 

In fact, I'll go a step further. I'll also include a drop-down that shows two-letter country codes next their respective flags. When the user selects a country code, the application will automagically show that country's country code for the phone number.

If you'd like to, you can go straight to the source on GitHub. Or you can keep reading.

Remember: if you're having problems running the Angular app locally, be sure to check the README.

Please note: this guide is a part of an ongoing series of tutorials on how to create a customer relationship management (CRM) application from scratch. The GitHub code for each guide belongs to a specific branch. The master branch holds the entire application up to the current date. If you want to follow the whole series, just view the careydevelopmentcrm tag. Note that the website displays the guides in reverse chronological order so if you want to start from the beginning, go to the last page.

The Business Requirements

Your boss Smithers walks into your office. He sits down and says, "Let's talk about phone numbers in that CRM app."

"When a user enters contact details, I want you to format the contact's phone number as it's being typed," he continues.

Smithers stares at the ceiling for a moment, making you slightly uncomfortable.

"You know what," he says. "I think I'd also like the app to support phone number formatting for every country in the world!"

Then he looks at you.

"You can do that, can't you?"

He gets up and walks away before you have a chance to answer.

Legal Immigration

You gotta start this assignment by doing some imports.

So go to your command line, navigate to the root of your CRM source, and type the following:

npm i libphonenumber-js

When that's done, do this import:

npm i ngx-flag-picker

Now let me explain what both of those imports will do for you.

The libphonenumber-js import is a JavaScript port of Google Android's libphonenumber library. That's the library you'll use to format the phone number.

The other library, ngx-flag-picker, will give you a dropdown of two-letter country codes with pretty flags next to them.

When you've implemented a dropdown with the assistance of that library, it will look like this:

Kinda cool, huh?

Okay but before you can make that happen, there's one more thing to do. Open index.html and add a <link>.

<link href="https://cdnjs.cloudflare.com/ajax/libs/flag-icon-css/3.5.0/css/flag-icon.min.css" rel="stylesheet">

The whole file should look like this:

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Carey Development CRM</title>
  <base href="/">

  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" type="image/x-icon" href="favicon.ico">
  <link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500&display=swap" rel="stylesheet">
  <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
  <link href="https://cdnjs.cloudflare.com/ajax/libs/flag-icon-css/3.5.0/css/flag-icon.min.css" rel="stylesheet">
</head>
<body>
  <app-root></app-root>
</body>
</html>

It's not often you need to update index.html, but here you are.

That CSS link, by the way, is where the country flag icons com from. So it's a pretty big deal for this solution.

The Prime Directive

Now you need to create a directive.

But what the heck is a directive?

In Angular, directives manipulate the underlying Document Object Model (DOM). You've used them many times before.

Components, for example, are directives. So any time you code a component you're techically creating a directive.

Also, those *ngIf and *ngFor things you see in Angular templates are directives as well. They're called structural directives because they determine the composition of the HTML output.

Finally, there are also attribute directives. You'll create one of those today.

Attribute directives appear as attributes in the HTML code. That attribute acts as a selector so the underlying TypeScript object knows how to find the element. Once it's located the element, it can manipulate the DOM.

The purpose of the directive you'll create today is to update the input field where the user types in a phone number. The code will change the input to the format preferred by the country the user selected.

The directive is called PhoneMaskDirective. Make it look like this:

import { Directive, HostListener, Input } from '@angular/core';
import { NgControl } from '@angular/forms';
import { AsYouType } from 'libphonenumber-js'

@Directive({
  selector: '[appPhoneMask]',
})
export class PhoneMaskDirective {

  @Input('countryCode') countryCode: string;

  constructor(public ngControl: NgControl) { }

  @HostListener('ngModelChange', ['$event'])
  onModelChange(event) {
    this.onInputChange(event, false);
  }

  @HostListener('keydown.backspace', ['$event'])
  keydownBackspace(event) {
    this.onInputChange(event.target.value, true);
  }

  onInputChange(event, backspace) {
    let asYouType: AsYouType = this.getFormatter();

    if (asYouType) {
      let newVal = event.replace(/\D/g, '');

      if (backspace && newVal.length <= 6) {
        newVal = newVal.substring(0, newVal.length - 1);
      }

      newVal = asYouType.input(newVal);

      this.ngControl.valueAccessor.writeValue(newVal);
    }
  }

  private getFormatter(): AsYouType {
    let asYouType: AsYouType = null;

    switch (this.countryCode) {
      case 'us':
        asYouType = new AsYouType('US');
        break;
      case 'jp':
        asYouType = new AsYouType('JP');
        break;
      case 'ca':
        asYouType = new AsYouType('CA');
        break;
      case 'ph':
        asYouType = new AsYouType('PH');
        break;
      case 'mx':
        asYouType = new AsYouType('MX');
        break;
      case 'be':
        asYouType = new AsYouType('BE');
        break;
      case 'lu':
        asYouType = new AsYouType('LU');
        break;
      case 'de':
        asYouType = new AsYouType('DE');
        break;
      case 'br':
        asYouType = new AsYouType('BR');
        break;
    }

    return asYouType;
  }
}

Note for starters the @Directive annotation. That tells Angular that this object is a directive.

The selector property specifies element that qualify for this directive. In this case, the only elements that qualify are elements with appPhoneMask attributes.

That @HostListener annotation you see above the onModelChange() event listens for a specific DOM event. In this case, that DOM event is ngModelChange.

If you dig into the weeds in the Angular docs, you'll find that ngModelChange gets fired before change detection begins on an element. So that's the event you're looking for when you want to format user input as it's typed.

That @HostListener annocation also accepts one or more arguments. Here, the code accepts a single argument: event.

And what is that event? It's the user input. It's a string that gets manipulated to format the phone number.

Likewise, the keydownBackspace() method also listens for a DOM event: a backspace.

The code handles backspaces specifically so users can backspace over the non-digit characters that get inserted into the input field (like parentheses).

The onInputChange() method begins by instantiating AsYouType. That's a class you get from the libphonenumber-js library. It lives up to its name by formatting the phone number as the user types it.

That class gets instantiated via the getFormatter() method. At first blush, you might think that method is inefficient.

Maybe you're asking yourself: why is the code using a switch statement? Why not just instantiate AsYouType by passing the country code into the constructor?

Unfortunately, AsYouType doesn't accept just any string in the constructor. It accepts only one of 218 different two-character combinations.

So the bottom line is you have to instantiate it programatically (yuck) or use the switch statement. This code uses the second option.

Keep in mind, though: this solution only supports a limited number of countries to keep things simple. You can see them in that method.

If you want to support all 218 country codes, you're going to end up with one very large switch block. In that case, you might want to look for another option.

Once AsYouType gets instantiated, the code strips out all non-digit characters from the phone number. That's the let newVal = event.replace(/\D/g, '') line that you see above.

Then, the code checks for a backspace and updates the input accordingly.

After that, it uses AsYouType to format the user's input according to the selected country code.

Finally, it writes that new string back to the input field.

Mod Men

Next, you need to update contacts.module.ts to add a new module and your new directive.

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ContactFormComponent } from './contact-form/contact-form.component';
import { AddContactComponent } from './add-contact/add-contact.component';
import { RouterModule } from '@angular/router';
import { FlexLayoutModule } from '@angular/flex-layout';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatButtonModule } from '@angular/material/button';
import { MatStepperModule } from '@angular/material/stepper';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatSelectModule } from '@angular/material/select';
import { MatRadioModule } from '@angular/material/radio';
import { MatExpansionModule } from '@angular/material/expansion';
import { ReactiveFormsModule } from '@angular/forms';
import { AlertModule } from '../../ui/alert/alert.module';
import { BasicInfoFormComponent } from './contact-form/basic-info-form/basic-info-form.component';
import { AddressesFormComponent } from './contact-form/addresses-form/addresses-form.component';
import { PhonesFormComponent } from './contact-form/phones-form/phones-form.component';
import { AddressTypeFormComponent } from './contact-form/addresses-form/address-type-form/address-type-form.component';
import { PhoneTypeFormComponent } from './contact-form/phones-form/phone-type-form/phone-type-form.component';
import { ReviewFormComponent } from './contact-form/review-form/review-form.component';
import { NgxFlagPickerModule } from 'ngx-flag-picker';
import { PhoneMaskDirective } from '../../util/phone-mask.directive';

export const routes = [
  { path: '', pathMatch: 'full', redirectTo: 'account-info' },
  { path: 'add-contact', component: AddContactComponent }
];

@NgModule({
  declarations: [
    ContactFormComponent,
    AddContactComponent,
    BasicInfoFormComponent,
    AddressesFormComponent,
    PhonesFormComponent,
    AddressTypeFormComponent,
    PhoneTypeFormComponent,
    ReviewFormComponent,
    PhoneMaskDirective
  ],
  imports: [
    CommonModule,
    FlexLayoutModule,
    MatIconModule,
    MatInputModule,
    MatButtonModule,
    MatProgressSpinnerModule,
    MatSelectModule,
    MatStepperModule,
    MatRadioModule,
    MatExpansionModule,
    ReactiveFormsModule,
    AlertModule,
    NgxFlagPickerModule,
    RouterModule.forChild(routes)
  ]
})

export class ContactsModule { }

The NgxFlagPickerModule import enables you to add a dropdown that associates country flags with two-letter country codes. 

The PhoneMaskDirective import enables you to use the directive you just created.

Telefono

Now edit phone-type-form.component.ts. Start by adding a few new fields.

  selectedCountryCode = 'us';
  phoneCode = '1';
  countryCodes = ['us', 'ca', 'de', 'mx', 'br', 'pt', 'cn', 'be', 'jp', 'ph', 'lu', 'bs'];

The selectedCountryCode field reflects the two-letter country code that the country that the user selected. It defaults to the United States.

Similarly, the phoneCode field reflects the phone prefix of the country that the user selected. It defaults to 1 for the United States.

The countryCodes array includes the two-digit country codes of all the available countries that the user can select. Feel free to update that to include as many or as few countries as you like. 

Just keep in mind that the two-letter codes must be in lower case. That's a requirement for NgxFlagPickerModule.

You'll also need a new method:

  changeSelectedCountryCode(value: string): void {
    this.selectedCountryCode = value;
    this.phoneCode = this.geoService.findCountryCodeByTwoLetterAbbreviation(this.selectedCountryCode);
  }

That method gets called every time the user changes the country for the phone number.

First, it sets the selectedCountryCode field that you just looked at.

Then, it uses GeoService to get that country's phone prefix based on the two-letter country code.

GeoService is a little API I've written to grab important geo-related info. It exposes endpoints that get all states and countries as well as important info about each (such as the phone prefix for each country).

If you don't have anything like GeoService, you can do the lookup locally from a hardcoded array of country info. Here's what that array looks like:

[{"name":"Afghanistan","twoLetterCode":"AF","threeLetterCode":"AFG","countryCode":"93"},{"name":"Albania","twoLetterCode":"AL","threeLetterCode":"ALB","countryCode":"355"},{"name":"Algeria","twoLetterCode":"DZ","threeLetterCode":"DZA","countryCode":"213"},{"name":"American Samoa","twoLetterCode":"AS","threeLetterCode":"ASM","countryCode":"1-684"},{"name":"Andorra","twoLetterCode":"AD","threeLetterCode":"AND","countryCode":"376"},{"name":"Angola","twoLetterCode":"AO","threeLetterCode":"AGO","countryCode":"244"},{"name":"Anguilla","twoLetterCode":"AI","threeLetterCode":"AIA","countryCode":"1-264"},{"name":"Antarctica","twoLetterCode":"AQ","threeLetterCode":"ATA","countryCode":"672"},{"name":"Antigua and Barbuda","twoLetterCode":"AG","threeLetterCode":"ATG","countryCode":"1-268"},{"name":"Argentina","twoLetterCode":"AR","threeLetterCode":"ARG","countryCode":"54"},{"name":"Armenia","twoLetterCode":"AM","threeLetterCode":"ARM","countryCode":"374"},{"name":"Aruba","twoLetterCode":"AW","threeLetterCode":"ABW","countryCode":"297"},{"name":"Australia","twoLetterCode":"AU","threeLetterCode":"AUS","countryCode":"61"},{"name":"Austria","twoLetterCode":"AT","threeLetterCode":"AUT","countryCode":"43"},{"name":"Azerbaijan","twoLetterCode":"AZ","threeLetterCode":"AZE","countryCode":"994"},{"name":"Bahamas","twoLetterCode":"BS","threeLetterCode":"BHS","countryCode":"1-242"},{"name":"Bahrain","twoLetterCode":"BH","threeLetterCode":"BHR","countryCode":"973"},{"name":"Bangladesh","twoLetterCode":"BD","threeLetterCode":"BGD","countryCode":"880"},{"name":"Barbados","twoLetterCode":"BB","threeLetterCode":"BRB","countryCode":"1-246"},{"name":"Belarus","twoLetterCode":"BY","threeLetterCode":"BLR","countryCode":"375"},{"name":"Belgium","twoLetterCode":"BE","threeLetterCode":"BEL","countryCode":"32"},{"name":"Belize","twoLetterCode":"BZ","threeLetterCode":"BLZ","countryCode":"501"},{"name":"Benin","twoLetterCode":"BJ","threeLetterCode":"BEN","countryCode":"229"},{"name":"Bermuda","twoLetterCode":"BM","threeLetterCode":"BMU","countryCode":"1-441"},{"name":"Bhutan","twoLetterCode":"BT","threeLetterCode":"BTN","countryCode":"975"},{"name":"Bolivia","twoLetterCode":"BO","threeLetterCode":"BOL","countryCode":"591"},{"name":"Bosnia and Herzegovina","twoLetterCode":"BA","threeLetterCode":"BIH","countryCode":"387"},{"name":"Botswana","twoLetterCode":"BW","threeLetterCode":"BWA","countryCode":"267"},{"name":"Brazil","twoLetterCode":"BR","threeLetterCode":"BRA","countryCode":"55"},{"name":"British Indian Ocean Territory","twoLetterCode":"IO","threeLetterCode":"IOT","countryCode":"246"},{"name":"British Virgin Islands","twoLetterCode":"VG","threeLetterCode":"VGB","countryCode":"1-284"},{"name":"Brunei","twoLetterCode":"BN","threeLetterCode":"BRN","countryCode":"673"},{"name":"Bulgaria","twoLetterCode":"BG","threeLetterCode":"BGR","countryCode":"359"},{"name":"Burkina Faso","twoLetterCode":"BF","threeLetterCode":"BFA","countryCode":"226"},{"name":"Burundi","twoLetterCode":"BI","threeLetterCode":"BDI","countryCode":"257"},{"name":"Cambodia","twoLetterCode":"KH","threeLetterCode":"KHM","countryCode":"855"},{"name":"Cameroon","twoLetterCode":"CM","threeLetterCode":"CMR","countryCode":"237"},{"name":"Canada","twoLetterCode":"CA","threeLetterCode":"CAN","countryCode":"1"},{"name":"Cape Verde","twoLetterCode":"CV","threeLetterCode":"CPV","countryCode":"238"},{"name":"Cayman Islands","twoLetterCode":"KY","threeLetterCode":"CYM","countryCode":"1-345"},{"name":"Central African Republic","twoLetterCode":"CF","threeLetterCode":"CAF","countryCode":"236"},{"name":"Chad","twoLetterCode":"TD","threeLetterCode":"TCD","countryCode":"235"},{"name":"Chile","twoLetterCode":"CL","threeLetterCode":"CHL","countryCode":"56"},{"name":"China","twoLetterCode":"CN","threeLetterCode":"CHN","countryCode":"86"},{"name":"Christmas Island","twoLetterCode":"CX","threeLetterCode":"CXR","countryCode":"61"},{"name":"Cocos Islands","twoLetterCode":"CC","threeLetterCode":"CCK","countryCode":"61"},{"name":"Colombia","twoLetterCode":"CO","threeLetterCode":"COL","countryCode":"57"},{"name":"Comoros","twoLetterCode":"KM","threeLetterCode":"COM","countryCode":"269"},{"name":"Cook Islands","twoLetterCode":"CK","threeLetterCode":"COK","countryCode":"682"},{"name":"Costa Rica","twoLetterCode":"CR","threeLetterCode":"CRI","countryCode":"506"},{"name":"Croatia","twoLetterCode":"HR","threeLetterCode":"HRV","countryCode":"385"},{"name":"Cuba","twoLetterCode":"CU","threeLetterCode":"CUB","countryCode":"53"},{"name":"Curacao","twoLetterCode":"CW","threeLetterCode":"CUW","countryCode":"599"},{"name":"Cyprus","twoLetterCode":"CY","threeLetterCode":"CYP","countryCode":"357"},{"name":"Czech Republic","twoLetterCode":"CZ","threeLetterCode":"CZE","countryCode":"420"},{"name":"Democratic Republic of the Congo","twoLetterCode":"CD","threeLetterCode":"COD","countryCode":"243"},{"name":"Denmark","twoLetterCode":"DK","threeLetterCode":"DNK","countryCode":"45"},{"name":"Djibouti","twoLetterCode":"DJ","threeLetterCode":"DJI","countryCode":"253"},{"name":"Dominica","twoLetterCode":"DM","threeLetterCode":"DMA","countryCode":"1-767"},{"name":"Dominican Republic","twoLetterCode":"DO","threeLetterCode":"DOM","countryCode":"1-809, 1-829, 1-849"},{"name":"East Timor","twoLetterCode":"TL","threeLetterCode":"TLS","countryCode":"670"},{"name":"Ecuador","twoLetterCode":"EC","threeLetterCode":"ECU","countryCode":"593"},{"name":"Egypt","twoLetterCode":"EG","threeLetterCode":"EGY","countryCode":"20"},{"name":"El Salvador","twoLetterCode":"SV","threeLetterCode":"SLV","countryCode":"503"},{"name":"Equatorial Guinea","twoLetterCode":"GQ","threeLetterCode":"GNQ","countryCode":"240"},{"name":"Eritrea","twoLetterCode":"ER","threeLetterCode":"ERI","countryCode":"291"},{"name":"Estonia","twoLetterCode":"EE","threeLetterCode":"EST","countryCode":"372"},{"name":"Ethiopia","twoLetterCode":"ET","threeLetterCode":"ETH","countryCode":"251"},{"name":"Falkland Islands","twoLetterCode":"FK","threeLetterCode":"FLK","countryCode":"500"},{"name":"Faroe Islands","twoLetterCode":"FO","threeLetterCode":"FRO","countryCode":"298"},{"name":"Fiji","twoLetterCode":"FJ","threeLetterCode":"FJI","countryCode":"679"},{"name":"Finland","twoLetterCode":"FI","threeLetterCode":"FIN","countryCode":"358"},{"name":"France","twoLetterCode":"FR","threeLetterCode":"FRA","countryCode":"33"},{"name":"French Polynesia","twoLetterCode":"PF","threeLetterCode":"PYF","countryCode":"689"},{"name":"Gabon","twoLetterCode":"GA","threeLetterCode":"GAB","countryCode":"241"},{"name":"Gambia","twoLetterCode":"GM","threeLetterCode":"GMB","countryCode":"220"},{"name":"Georgia","twoLetterCode":"GE","threeLetterCode":"GEO","countryCode":"995"},{"name":"Germany","twoLetterCode":"DE","threeLetterCode":"DEU","countryCode":"49"},{"name":"Ghana","twoLetterCode":"GH","threeLetterCode":"GHA","countryCode":"233"},{"name":"Gibraltar","twoLetterCode":"GI","threeLetterCode":"GIB","countryCode":"350"},{"name":"Greece","twoLetterCode":"GR","threeLetterCode":"GRC","countryCode":"30"},{"name":"Greenland","twoLetterCode":"GL","threeLetterCode":"GRL","countryCode":"299"},{"name":"Grenada","twoLetterCode":"GD","threeLetterCode":"GRD","countryCode":"1-473"},{"name":"Guam","twoLetterCode":"GU","threeLetterCode":"GUM","countryCode":"1-671"},{"name":"Guatemala","twoLetterCode":"GT","threeLetterCode":"GTM","countryCode":"502"},{"name":"Guernsey","twoLetterCode":"GG","threeLetterCode":"GGY","countryCode":"44-1481"},{"name":"Guinea","twoLetterCode":"GN","threeLetterCode":"GIN","countryCode":"224"},{"name":"Guinea-Bissau","twoLetterCode":"GW","threeLetterCode":"GNB","countryCode":"245"},{"name":"Guyana","twoLetterCode":"GY","threeLetterCode":"GUY","countryCode":"592"},{"name":"Haiti","twoLetterCode":"HT","threeLetterCode":"HTI","countryCode":"509"},{"name":"Honduras","twoLetterCode":"HN","threeLetterCode":"HND","countryCode":"504"},{"name":"Hong Kong","twoLetterCode":"HK","threeLetterCode":"HKG","countryCode":"852"},{"name":"Hungary","twoLetterCode":"HU","threeLetterCode":"HUN","countryCode":"36"},{"name":"Iceland","twoLetterCode":"IS","threeLetterCode":"ISL","countryCode":"354"},{"name":"India","twoLetterCode":"IN","threeLetterCode":"IND","countryCode":"91"},{"name":"Indonesia","twoLetterCode":"ID","threeLetterCode":"IDN","countryCode":"62"},{"name":"Iran","twoLetterCode":"IR","threeLetterCode":"IRN","countryCode":"98"},{"name":"Iraq","twoLetterCode":"IQ","threeLetterCode":"IRQ","countryCode":"964"},{"name":"Ireland","twoLetterCode":"IE","threeLetterCode":"IRL","countryCode":"353"},{"name":"Isle of Man","twoLetterCode":"IM","threeLetterCode":"IMN","countryCode":"44-1624"},{"name":"Israel","twoLetterCode":"IL","threeLetterCode":"ISR","countryCode":"972"},{"name":"Italy","twoLetterCode":"IT","threeLetterCode":"ITA","countryCode":"39"},{"name":"Ivory Coast","twoLetterCode":"CI","threeLetterCode":"CIV","countryCode":"225"},{"name":"Jamaica","twoLetterCode":"JM","threeLetterCode":"JAM","countryCode":"1-876"},{"name":"Japan","twoLetterCode":"JP","threeLetterCode":"JPN","countryCode":"81"},{"name":"Jersey","twoLetterCode":"JE","threeLetterCode":"JEY","countryCode":"44-1534"},{"name":"Jordan","twoLetterCode":"JO","threeLetterCode":"JOR","countryCode":"962"},{"name":"Kazakhstan","twoLetterCode":"KZ","threeLetterCode":"KAZ","countryCode":"7"},{"name":"Kenya","twoLetterCode":"KE","threeLetterCode":"KEN","countryCode":"254"},{"name":"Kiribati","twoLetterCode":"KI","threeLetterCode":"KIR","countryCode":"686"},{"name":"Kosovo","twoLetterCode":"XK","threeLetterCode":"XKX","countryCode":"383"},{"name":"Kuwait","twoLetterCode":"KW","threeLetterCode":"KWT","countryCode":"965"},{"name":"Kyrgyzstan","twoLetterCode":"KG","threeLetterCode":"KGZ","countryCode":"996"},{"name":"Laos","twoLetterCode":"LA","threeLetterCode":"LAO","countryCode":"856"},{"name":"Latvia","twoLetterCode":"LV","threeLetterCode":"LVA","countryCode":"371"},{"name":"Lebanon","twoLetterCode":"LB","threeLetterCode":"LBN","countryCode":"961"},{"name":"Lesotho","twoLetterCode":"LS","threeLetterCode":"LSO","countryCode":"266"},{"name":"Liberia","twoLetterCode":"LR","threeLetterCode":"LBR","countryCode":"231"},{"name":"Libya","twoLetterCode":"LY","threeLetterCode":"LBY","countryCode":"218"},{"name":"Liechtenstein","twoLetterCode":"LI","threeLetterCode":"LIE","countryCode":"423"},{"name":"Lithuania","twoLetterCode":"LT","threeLetterCode":"LTU","countryCode":"370"},{"name":"Luxembourg","twoLetterCode":"LU","threeLetterCode":"LUX","countryCode":"352"},{"name":"Macau","twoLetterCode":"MO","threeLetterCode":"MAC","countryCode":"853"},{"name":"Macedonia","twoLetterCode":"MK","threeLetterCode":"MKD","countryCode":"389"},{"name":"Madagascar","twoLetterCode":"MG","threeLetterCode":"MDG","countryCode":"261"},{"name":"Malawi","twoLetterCode":"MW","threeLetterCode":"MWI","countryCode":"265"},{"name":"Malaysia","twoLetterCode":"MY","threeLetterCode":"MYS","countryCode":"60"},{"name":"Maldives","twoLetterCode":"MV","threeLetterCode":"MDV","countryCode":"960"},{"name":"Mali","twoLetterCode":"ML","threeLetterCode":"MLI","countryCode":"223"},{"name":"Malta","twoLetterCode":"MT","threeLetterCode":"MLT","countryCode":"356"},{"name":"Marshall Islands","twoLetterCode":"MH","threeLetterCode":"MHL","countryCode":"692"},{"name":"Mauritania","twoLetterCode":"MR","threeLetterCode":"MRT","countryCode":"222"},{"name":"Mauritius","twoLetterCode":"MU","threeLetterCode":"MUS","countryCode":"230"},{"name":"Mayotte","twoLetterCode":"YT","threeLetterCode":"MYT","countryCode":"262"},{"name":"Mexico","twoLetterCode":"MX","threeLetterCode":"MEX","countryCode":"52"},{"name":"Micronesia","twoLetterCode":"FM","threeLetterCode":"FSM","countryCode":"691"},{"name":"Moldova","twoLetterCode":"MD","threeLetterCode":"MDA","countryCode":"373"},{"name":"Monaco","twoLetterCode":"MC","threeLetterCode":"MCO","countryCode":"377"},{"name":"Mongolia","twoLetterCode":"MN","threeLetterCode":"MNG","countryCode":"976"},{"name":"Montenegro","twoLetterCode":"ME","threeLetterCode":"MNE","countryCode":"382"},{"name":"Montserrat","twoLetterCode":"MS","threeLetterCode":"MSR","countryCode":"1-664"},{"name":"Morocco","twoLetterCode":"MA","threeLetterCode":"MAR","countryCode":"212"},{"name":"Mozambique","twoLetterCode":"MZ","threeLetterCode":"MOZ","countryCode":"258"},{"name":"Myanmar","twoLetterCode":"MM","threeLetterCode":"MMR","countryCode":"95"},{"name":"Namibia","twoLetterCode":"NA","threeLetterCode":"NAM","countryCode":"264"},{"name":"Nauru","twoLetterCode":"NR","threeLetterCode":"NRU","countryCode":"674"},{"name":"Nepal","twoLetterCode":"NP","threeLetterCode":"NPL","countryCode":"977"},{"name":"Netherlands","twoLetterCode":"NL","threeLetterCode":"NLD","countryCode":"31"},{"name":"Netherlands Antilles","twoLetterCode":"AN","threeLetterCode":"ANT","countryCode":"599"},{"name":"New Caledonia","twoLetterCode":"NC","threeLetterCode":"NCL","countryCode":"687"},{"name":"New Zealand","twoLetterCode":"NZ","threeLetterCode":"NZL","countryCode":"64"},{"name":"Nicaragua","twoLetterCode":"NI","threeLetterCode":"NIC","countryCode":"505"},{"name":"Niger","twoLetterCode":"NE","threeLetterCode":"NER","countryCode":"227"},{"name":"Nigeria","twoLetterCode":"NG","threeLetterCode":"NGA","countryCode":"234"},{"name":"Niue","twoLetterCode":"NU","threeLetterCode":"NIU","countryCode":"683"},{"name":"North Korea","twoLetterCode":"KP","threeLetterCode":"PRK","countryCode":"850"},{"name":"Northern Mariana Islands","twoLetterCode":"MP","threeLetterCode":"MNP","countryCode":"1-670"},{"name":"Norway","twoLetterCode":"NO","threeLetterCode":"NOR","countryCode":"47"},{"name":"Oman","twoLetterCode":"OM","threeLetterCode":"OMN","countryCode":"968"},{"name":"Pakistan","twoLetterCode":"PK","threeLetterCode":"PAK","countryCode":"92"},{"name":"Palau","twoLetterCode":"PW","threeLetterCode":"PLW","countryCode":"680"},{"name":"Palestine","twoLetterCode":"PS","threeLetterCode":"PSE","countryCode":"970"},{"name":"Panama","twoLetterCode":"PA","threeLetterCode":"PAN","countryCode":"507"},{"name":"Papua New Guinea","twoLetterCode":"PG","threeLetterCode":"PNG","countryCode":"675"},{"name":"Paraguay","twoLetterCode":"PY","threeLetterCode":"PRY","countryCode":"595"},{"name":"Peru","twoLetterCode":"PE","threeLetterCode":"PER","countryCode":"51"},{"name":"Philippines","twoLetterCode":"PH","threeLetterCode":"PHL","countryCode":"63"},{"name":"Pitcairn","twoLetterCode":"PN","threeLetterCode":"PCN","countryCode":"64"},{"name":"Poland","twoLetterCode":"PL","threeLetterCode":"POL","countryCode":"48"},{"name":"Portugal","twoLetterCode":"PT","threeLetterCode":"PRT","countryCode":"351"},{"name":"Puerto Rico","twoLetterCode":"PR","threeLetterCode":"PRI","countryCode":"1-787, 1-939"},{"name":"Qatar","twoLetterCode":"QA","threeLetterCode":"QAT","countryCode":"974"},{"name":"Republic of the Congo","twoLetterCode":"CG","threeLetterCode":"COG","countryCode":"242"},{"name":"Reunion","twoLetterCode":"RE","threeLetterCode":"REU","countryCode":"262"},{"name":"Romania","twoLetterCode":"RO","threeLetterCode":"ROU","countryCode":"40"},{"name":"Russia","twoLetterCode":"RU","threeLetterCode":"RUS","countryCode":"7"},{"name":"Rwanda","twoLetterCode":"RW","threeLetterCode":"RWA","countryCode":"250"},{"name":"Saint Barthelemy","twoLetterCode":"BL","threeLetterCode":"BLM","countryCode":"590"},{"name":"Saint Helena","twoLetterCode":"SH","threeLetterCode":"SHN","countryCode":"290"},{"name":"Saint Kitts and Nevis","twoLetterCode":"KN","threeLetterCode":"KNA","countryCode":"1-869"},{"name":"Saint Lucia","twoLetterCode":"LC","threeLetterCode":"LCA","countryCode":"1-758"},{"name":"Saint Martin","twoLetterCode":"MF","threeLetterCode":"MAF","countryCode":"590"},{"name":"Saint Pierre and Miquelon","twoLetterCode":"PM","threeLetterCode":"SPM","countryCode":"508"},{"name":"Saint Vincent and the Grenadines","twoLetterCode":"VC","threeLetterCode":"VCT","countryCode":"1-784"},{"name":"Samoa","twoLetterCode":"WS","threeLetterCode":"WSM","countryCode":"685"},{"name":"San Marino","twoLetterCode":"SM","threeLetterCode":"SMR","countryCode":"378"},{"name":"Sao Tome and Principe","twoLetterCode":"ST","threeLetterCode":"STP","countryCode":"239"},{"name":"Saudi Arabia","twoLetterCode":"SA","threeLetterCode":"SAU","countryCode":"966"},{"name":"Senegal","twoLetterCode":"SN","threeLetterCode":"SEN","countryCode":"221"},{"name":"Serbia","twoLetterCode":"RS","threeLetterCode":"SRB","countryCode":"381"},{"name":"Seychelles","twoLetterCode":"SC","threeLetterCode":"SYC","countryCode":"248"},{"name":"Sierra Leone","twoLetterCode":"SL","threeLetterCode":"SLE","countryCode":"232"},{"name":"Singapore","twoLetterCode":"SG","threeLetterCode":"SGP","countryCode":"65"},{"name":"Sint Maarten","twoLetterCode":"SX","threeLetterCode":"SXM","countryCode":"1-721"},{"name":"Slovakia","twoLetterCode":"SK","threeLetterCode":"SVK","countryCode":"421"},{"name":"Slovenia","twoLetterCode":"SI","threeLetterCode":"SVN","countryCode":"386"},{"name":"Solomon Islands","twoLetterCode":"SB","threeLetterCode":"SLB","countryCode":"677"},{"name":"Somalia","twoLetterCode":"SO","threeLetterCode":"SOM","countryCode":"252"},{"name":"South Africa","twoLetterCode":"ZA","threeLetterCode":"ZAF","countryCode":"27"},{"name":"South Korea","twoLetterCode":"KR","threeLetterCode":"KOR","countryCode":"82"},{"name":"South Sudan","twoLetterCode":"SS","threeLetterCode":"SSD","countryCode":"211"},{"name":"Spain","twoLetterCode":"ES","threeLetterCode":"ESP","countryCode":"34"},{"name":"Sri Lanka","twoLetterCode":"LK","threeLetterCode":"LKA","countryCode":"94"},{"name":"Sudan","twoLetterCode":"SD","threeLetterCode":"SDN","countryCode":"249"},{"name":"Suriname","twoLetterCode":"SR","threeLetterCode":"SUR","countryCode":"597"},{"name":"Svalbard and Jan Mayen","twoLetterCode":"SJ","threeLetterCode":"SJM","countryCode":"47"},{"name":"Swaziland","twoLetterCode":"SZ","threeLetterCode":"SWZ","countryCode":"268"},{"name":"Sweden","twoLetterCode":"SE","threeLetterCode":"SWE","countryCode":"46"},{"name":"Switzerland","twoLetterCode":"CH","threeLetterCode":"CHE","countryCode":"41"},{"name":"Syria","twoLetterCode":"SY","threeLetterCode":"SYR","countryCode":"963"},{"name":"Taiwan","twoLetterCode":"TW","threeLetterCode":"TWN","countryCode":"886"},{"name":"Tajikistan","twoLetterCode":"TJ","threeLetterCode":"TJK","countryCode":"992"},{"name":"Tanzania","twoLetterCode":"TZ","threeLetterCode":"TZA","countryCode":"255"},{"name":"Thailand","twoLetterCode":"TH","threeLetterCode":"THA","countryCode":"66"},{"name":"Togo","twoLetterCode":"TG","threeLetterCode":"TGO","countryCode":"228"},{"name":"Tokelau","twoLetterCode":"TK","threeLetterCode":"TKL","countryCode":"690"},{"name":"Tonga","twoLetterCode":"TO","threeLetterCode":"TON","countryCode":"676"},{"name":"Trinidad and Tobago","twoLetterCode":"TT","threeLetterCode":"TTO","countryCode":"1-868"},{"name":"Tunisia","twoLetterCode":"TN","threeLetterCode":"TUN","countryCode":"216"},{"name":"Turkey","twoLetterCode":"TR","threeLetterCode":"TUR","countryCode":"90"},{"name":"Turkmenistan","twoLetterCode":"TM","threeLetterCode":"TKM","countryCode":"993"},{"name":"Turks and Caicos Islands","twoLetterCode":"TC","threeLetterCode":"TCA","countryCode":"1-649"},{"name":"Tuvalu","twoLetterCode":"TV","threeLetterCode":"TUV","countryCode":"688"},{"name":"U.S. Virgin Islands","twoLetterCode":"VI","threeLetterCode":"VIR","countryCode":"1-340"},{"name":"Uganda","twoLetterCode":"UG","threeLetterCode":"UGA","countryCode":"256"},{"name":"Ukraine","twoLetterCode":"UA","threeLetterCode":"UKR","countryCode":"380"},{"name":"United Arab Emirates","twoLetterCode":"AE","threeLetterCode":"ARE","countryCode":"971"},{"name":"United Kingdom","twoLetterCode":"GB","threeLetterCode":"GBR","countryCode":"44"},{"name":"United States","twoLetterCode":"US","threeLetterCode":"USA","countryCode":"1"},{"name":"Uruguay","twoLetterCode":"UY","threeLetterCode":"URY","countryCode":"598"},{"name":"Uzbekistan","twoLetterCode":"UZ","threeLetterCode":"UZB","countryCode":"998"},{"name":"Vanuatu","twoLetterCode":"VU","threeLetterCode":"VUT","countryCode":"678"},{"name":"Vatican","twoLetterCode":"VA","threeLetterCode":"VAT","countryCode":"379"},{"name":"Venezuela","twoLetterCode":"VE","threeLetterCode":"VEN","countryCode":"58"},{"name":"Vietnam","twoLetterCode":"VN","threeLetterCode":"VNM","countryCode":"84"},{"name":"Wallis and Futuna","twoLetterCode":"WF","threeLetterCode":"WLF","countryCode":"681"},{"name":"Western Sahara","twoLetterCode":"EH","threeLetterCode":"ESH","countryCode":"212"},{"name":"Yemen","twoLetterCode":"YE","threeLetterCode":"YEM","countryCode":"967"},{"name":"Zambia","twoLetterCode":"ZM","threeLetterCode":"ZMB","countryCode":"260"},{"name":"Zimbabwe","twoLetterCode":"ZW","threeLetterCode":"ZWE","countryCode":"263"}]

Marked Up

Now, update phone-type-form.component.html so it looks like this:

<form [formGroup]="phoneTypeFormGroup">

  <div class="vertical-form-field" fxLayout="column" fxFlex="50" fxFlex.lt-md="100">
    <mat-form-field appearance="fill" class="no-label-field" style="height:125px">
      <div fxLayout="row wrap">
        <div fxFlex="20" fxFlex.lt-md="100">
          <ngx-flag-picker [selectedCountryCode]="selectedCountryCode"
                            [countryCodes]="countryCodes"
                            (changedCountryCode)="changeSelectedCountryCode($event)">
          </ngx-flag-picker>
        </div>
        <div fxFlex="10" fxFlex.lt-md="20" [ngStyle.lt-md]="{'margin-top': '10px'}">
          {{phoneCode}}
        </div>
        <div fxFlex="60" fxFlex.lt-md="70" [ngStyle.lt-md]="{'margin-top': '10px'}">
          <input appPhoneMask formControlName="phone" [countryCode]="selectedCountryCode" matInput placeholder="Enter a phone number" maxlength="20">
        </div>
      </div>
      <mat-error *ngIf="phoneTypeFormGroup.controls['phone'].invalid">Please enter a valid phone number</mat-error>
    </mat-form-field>
  </div>
</form>

Pay attention to that <ngx-flag-picker> element. That's the dropdown that shows the flag icons with their respective two-letter country codes.

That element grabs a couple of inputs from the TypeScript code that you just looked at: selectedCountryCode and countryCodes. The selectedCountryCode input reflects the current selection in the dropdown. The countryCodes input is an array of all available country codes.

The <div> element just below that shows the selected country's phone prefix. That's in {{phoneCode}}

Finally, note that the <input> element includes an appPhoneMask attribute. That tells Angular to use the directive that you created a few sections back.

Experimental Phases

Now it's time to test this whole thing out. Fire up your Spring user and contact applications. Then, launch the CRM Angular app.

Visit http://localhost:4200/login and login with the usual credentials (darth/thedarkside).

Then, navigate to Contacts and Add Contact. Open up the Phones step in that wizard and you should see a new dropdown:

 

Select Brazil. You should see the Brazil international phone prefix (55) appear in the area just to the right of the drop down.

Now start typing a phone number. It should get formatted according to the way phone numbers look in Brazil.

 

Try it out with a few other countries.

Wrapping It Up

You done good. You've now got a solution that automatically formats phone numbers.

Now it's up to you. Look for ways to improve the code. Optimize the directive. Implement your own geo service.

As always, feel free to grab the source from GitHub so you can tinker with it.

Have fun!