1

I am using angular2, the new forms api.

I want to have a phone number input box, which is auto-formatted as the user types.

For example the user types:

12345678901

and as they enter a recognizable phone number it changes to

1 (234) 567-8901

I figured out how to add a directive onto the input control but I don't know how to inject myself into the input processing pipeline.

2 Answers 2

10

You have to create your own directive, take this one as an example, it transforms using Phone.format, storing the raw number in the model but displaying the formatted number to the user:

import { Directive } from '@angular/core'
import { NgControl } from '@angular/forms'
import { Phone } from '../phone'

@Directive({
  selector: '[ngModel][phone]',
  host: {
    '(ngModelChange)': 'onInputChange($event)',
    '(blur)': 'onBlur($event)'
  }
})
export class PhoneDirective {

  constructor (control: NgControl) {
    this.control = control
  }

  ngOnInit () {
    let formatted = Phone.format(this.control.model)
    setTimeout(() => this.control.valueAccessor.writeValue(formatted), 0)
  }

  onBlur () {
    let val = this.control.model
    let raw = val.replace(/\W/g, '')
    let formatted = Phone.format(raw)

    this.control.valueAccessor.writeValue(formatted)
    this.control.viewToModelUpdate(raw)
  }

  onInputChange (val) {
    let raw = val.replace(/\W/g, '')
    if (val !== raw) this.control.viewToModelUpdate(raw)
  }
}
Sign up to request clarification or add additional context in comments.

7 Comments

seems you are using angular2 beta for your code, PS: angular2/core has been changed with @angular/core etc.
btw i dont think this one is answer as per question.
I updated it with the latest forms code. It was pretty close, needed NgControl instead and (ngModelChange) event. Also it had a re-entrant update which infinite looped.
Im still having a problem where the formatting isn't happening when the directive is first launched. I tried using ngOnInit to get the this.control.value but it's always null. I'm not sure at what point the value gets initialized into the control.
Ok I basically figured out that you can use the this.control.model property in ngOnInit. However, you have to do it in setTimeout otherwise something else writes the value to the valueAccessor after this runs. Not sure if there is a cleaner way.
|
2

The directive below will format the value as you type:

  1. the input method is triggered when the user enters the value (view -> model, aka $parser in AngularJS). It makes sure the view gets formatted view (via render method) while the model gets raw numeric value (via propagateChange method).
  2. the writeValue method is triggered when the model is updated (model -> view, aka $formatter in AngularJS). It makes sure the view gets formatted view (via render method).
  3. the format method contains the logic to format the view. We need some extra conditions there to make sure the user can not only input characters but also remove them one by one.

To use the directive register it in the module and link it to the input, e.g.:

<input appFormattedNumber formControlName= .../>

(I have tested this with reactive forms only)

import {Directive, ElementRef, forwardRef, HostListener, Renderer2} from '@angular/core';
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms';

@Directive({
  selector: '[appFormattedNumber]',
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => FormattedNumberDirective),
      multi: true
    }
  ]
})
export class FormattedNumberDirective implements ControlValueAccessor {
  propagateChange;

  constructor( private renderer: Renderer2, private element: ElementRef ) {}

  @HostListener('input', [ '$event.target.value' ])
  input( value ) {
    const canonical = this.removeNonNumericCharacters(value);

    const formatted = this.format(canonical);
    this.render(formatted);

    this.propagateChange(canonical);
  }
  writeValue( value: any ): void {
    const canonical = this.removeNonNumericCharacters(value.toString());
    const formatted = this.format(canonical);
    this.render(formatted);
  }

  registerOnChange( fn: any ): void {
    this.propagateChange = fn;
  }

  registerOnTouched(fn: any): void {}

  setDisabledState(isDisabled: boolean): void {}

  private removeNonNumericCharacters(value): string {
    return value.replace(/\D/g, '');
  }

  private format(value): string {
    if (value.length < 5) {
      return value;
    }

    const int = value.substring(0, 1);
    const areaCode = value.substring(1, 4);
    const ext1 = value.substring(4, 7);
    const ext2 = value.substring(7, 11);
    const ext = ext2 ? ` ${ext1}-${ext2}` : `${ext1}`;

    return `${int} (${areaCode}) ${ext}`;
  }

  private render(value) {
    const element = this.element.nativeElement;
    this.renderer.setProperty(element, 'value', value);
  }
}

1 Comment

it works nice, however if I edit a character in the middle is moving the cursor to the end.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.