Search code examples
angulartypescriptangular-ngmodel

Angular two-way binding and mapping in UI


I have a dummy question... (complete noobie on frontend side)

I am building a small CRUD project with Angular 4 and TypeScript where I make calls to an API in order to get and update phone numbers based on an id.

The phone number which I get from the service is pretty standard and divided in 3 parts:

  1. country code
  2. predial
  3. actual number

But in the UI, I have actually two input fields - one for country code and predial together and a second one for the actual number. To visualize this, if the phone number I get from user is +445551234567 when I am editing it, I have two input fields with +44555 and 1234567

In other words, I have to map the country code + predial together in the UI in my edit page and also when I make the update call back to the API, I need to be able to map it again to 3 variables.

In ideal world, I would map this in my service, but that's unfortunately not possible due to other restrictions.

So I have these models/interfaces:

export interface Contacts {
    email: string;
    phoneNumber: PhoneNumber;
    faxNumber: PhoneNumber;
}

export interface PhoneNumber {
    countryCode: string;
    predial: string;
    number: string;
}

And then in my Input component I have a form:

this.form = new FormGroup({
     phoneCountryCodeAndPredial: new ValidatableFormControl(null, [Validators.required]),
     phoneNumber: new ValidatableFormControl(null, [Validators.required]),

     faxCountryCodeAndPredial: new ValidatableFormControl(null, [Validators.required]),
     faxNumber: new ValidatableFormControl(null, [Validators.required]),

     email: new ValidatableFormControl(null, [Validators.pattern(/\S+@\S+\.\S+/)])
});

And in the html just the phone number for brevity:

<div class="form-group-line">
   <input [formControl]="form.controls['phoneCountryCodeAndPredial']" name="countryCodeAndPredial"
   class="required"
   placeholder="Predial"
   [(ngModel)]=contacts.phoneNumber.countryCode/> 

   <input [formControl]="form.controls['phoneNumber']" name="phoneNumber"
   class="required"
   placeholder="Phone Number"
   [(ngModel)]="contacts.phoneNumber.number"/>
</div>

So I want two-way-data binding, that's why I am using the [(ngModel)] banana in the box, but how do I use two-way-data binding + mapping in the ngModel?

I cannot make a function that combines the country code and predial together and pass the value to the ngModel.

So how do I do this mapping in the UI? What is a good practice?


Solution

  • You could split up the ngModel binding:

    <text-input [formControl]="form.controls['phoneCountryCodeAndPredial']" 
      name="countryCodeAndPredial"
      class="required"
      placeholder="Predial"
      [ngModel]="contacts.phoneNumber.countryCode + contacts.phoneNumber.predial"
      (ngModelChange)="Utils.setCombinedNumber(contacts.phoneNumber, $event)"
    >
    

    And then set the correct fields on model change:

    setCombinedNumber(phoneNumber: PhoneNumber, combined: string) {
      // your actual splitting code here
      let pattern = new RegExp(/^(\+\d{2})(\d+)$/);
      if (pattern.test(combined)) {
        let match = pattern.exec(combined);
        phoneNumber.countryCode = match[1];
        phoneNumber.predial = match[2];
      }
      else {
        // do some error handling here? maybe show a toast?
      }
    }