在 Angular 组件包装器中创建 Phone 文本框

Create Phone Textbox into Angular Component Wrapper

我正在尝试将 Phone 掩码从下面的解决方案转换为 Angular 组件。有谁知道如何进行这个? * 任何为 Phone 文本框创建 1 个相似组件的答案都可以。

Mask for an Input to allow phone numbers?

https://stackblitz.com/edit/angular6-phone-mask

我试过了,将代码复制到下面的组件中。接收错误,

原来的答案使用指令,并且只适用于formcontrol。目标是让自定义公司文本框组件具有唯一的样式、输入等。

在底部,我们引用了 stackblitz 代码。

打字稿:

export class CustomFieldErrorMatcher implements ErrorStateMatcher {
  constructor(private customControl: FormControl,private errors:any) { }

  isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
    return this.customControl && this.customControl.touched &&(this.customControl.invalid || this.errors);
  }
}

@Component({
  selector: 'app-input-phone',
  templateUrl: './input-phone.component.html',
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => InputPhoneComponent),
      multi: true
    }
  ]})

export class InputPhoneComponent implements OnInit, ControlValueAccessor {
  @Input() MaxLength: string;
  @Input() ReadOnly: boolean;
  @Input() Value: string;
  @Input() type: string;
  @Input() Label: string;
  @Input() PlaceHolder: string;
  @Output() saveValue = new EventEmitter();
  @Output() onStateChange = new EventEmitter();
  @Input() errors: any = null;
  disabled: boolean;
  control: FormControl;

  constructor(public injector: Injector) {}
  ngOnInit() {  }

  ngAfterViewInit(): void {
    const ngControl: NgControl = this.injector.get(NgControl, null);
    if (ngControl) {
      setTimeout(() => {
        this.control = ngControl.control as FormControl;
      })
    }}

  saveValueAction(e) { this.saveValue.emit(e.target.value); }
  writeValue(value: any) { this.Value = value ? value : ''; }
  onChange(e) { this.Value = e; }
  onTouched() { this.onStateChange.emit(); }

  registerOnChange(fn: any) { this.onChange = fn; }
  registerOnTouched(fn: any) { this.onTouched = fn; }
  setDisabledState(isDisabled) { this.disabled = isDisabled; }

  errorMatcher() {
    return new CustomFieldErrorMatcher(this.control,this.errors)
  }

  readonly errorStateMatcher: ErrorStateMatcher = {
    isErrorState: (ctrl: FormControl) => (ctrl && ctrl.invalid)
  };

  //Section from Stackblitz reference

  @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 newVal = event.replace(/\D/g, '');
    if (backspace && newVal.length <= 6) {
      newVal = newVal.substring(0, newVal.length - 1);
    }
    if (newVal.length === 0) {
      newVal = '';
    } else if (newVal.length <= 3) {
      newVal = newVal.replace(/^(\d{0,3})/, '()');
    } else if (newVal.length <= 6) {
      newVal = newVal.replace(/^(\d{0,3})(\d{0,3})/, '() ()');
    } else if (newVal.length <= 10) {
      newVal = newVal.replace(/^(\d{0,3})(\d{0,3})(\d{0,4})/, '() ()-');
    } else {
      newVal = newVal.substring(0, 10);
      newVal = newVal.replace(/^(\d{0,3})(\d{0,3})(\d{0,4})/, '() ()-');
    }
    this.writeValue(newVal);
  }

}

HTML:

<div class="input-wrap">
    <mat-form-field>
        <mat-label>{{Label}}</mat-label>   
        <input 
            matInput 
            [attr.maxlength] = "MaxLength"
            [value]="Value ? Value : ''"
            [placeholder]="PlaceHolder ? PlaceHolder : ''"
            [readonly]="ReadOnly"
            [type]="type ? type: 'text'"
            [ngModel]="Value" 
            [errorStateMatcher]="errorMatcher()"

            (input)="onChange($event.target.value)"
            (blur)="onTouched()"
            (change)="saveValueAction($event)"
            (ngModelChange)="Value=$event"
        >
    </mat-form-field>
</div>

Working Forked StackBlitz

打字稿:

import { Component, forwardRef, OnInit, Input, Output, EventEmitter, Injector, HostListener, ViewChild, ElementRef } from '@angular/core';
import { FormControl, FormGroupDirective, NgForm, NG_VALUE_ACCESSOR, ControlValueAccessor, NgControl } from '@angular/forms';

export interface ErrorStateMatcher {
  isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean;
}

export class CustomFieldErrorMatcher implements ErrorStateMatcher {
  constructor(private customControl: FormControl, private errors: any) { }

  isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
    return this.customControl && this.customControl.touched && (this.customControl.invalid || this.errors);
  }
}

@Component({
  selector: 'app-input-phone',
  templateUrl: './input-phone.component.html',
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => InputPhoneComponent),
      multi: true
    }
  ]
})

export class InputPhoneComponent implements OnInit, ControlValueAccessor {
  disabled: boolean;
  control: FormControl;
  @Input() MaxLength: string;
  @Input() ReadOnly: boolean;
  @Input() value: string;
  @Input() type: string;
  @Input() Label: string;
  @Input() PlaceHolder: string;
  @Output() saveValue = new EventEmitter();
  @Output() stateChange = new EventEmitter();
  @Input() errors: any = null;
  @ViewChild('input', { static: true }) inputViewChild: ElementRef;

  readonly errorStateMatcher: ErrorStateMatcher = {
    isErrorState: (ctrl: FormControl) => (ctrl && ctrl.invalid)
  };

  constructor(public injector: Injector) { }
  ngOnInit() { }

  saveValueAction(e: any) { this.saveValue.emit(e.target.value); }
  writeValue(value: any) {
    this.value = value ? value : '';
    if (this.inputViewChild && this.inputViewChild.nativeElement) {
      if (this.value === undefined || this.value == null) {
        this.inputViewChild.nativeElement.value = '';
      } else {
        const maskValue = this.convertToMaskValue(this.value, false);
        this.inputViewChild.nativeElement.value = maskValue;
      }
    }
  }

  onModelChange: Function = () => { };
  onChange(e) { this.value = e; }
  onTouched() { this.stateChange.emit(); }

  registerOnChange(fn: () => void): void {
    this.onModelChange = fn;
  }
  registerOnTouched(fn: any) { this.onTouched = fn; }
  setDisabledState(isDisabled) { this.disabled = isDisabled; }

  errorMatcher() {
    return new CustomFieldErrorMatcher(this.control, this.errors);
  }

  onInputChange(event) {
    setTimeout(() => {
      const maskValue = this.convertToMaskValue(event.target.value, event.inputType === 'deleteContentBackward');
      this.inputViewChild.nativeElement.value = maskValue;
      this.value = this.convertToRealValue(maskValue);
      this.onModelChange(this.value);
    }, 0);
  }

  private convertToMaskValue(value: string, backspace: boolean): string {
    let newVal = value;
    if (newVal && newVal.length > 0) {
      if (backspace && value.length <= 12) {
        newVal = value.substring(0, value.length - 1);
      }
      newVal = this.convertToRealValue(newVal);
      if (newVal.length === 0) {
        newVal = '';
      } else if (newVal.length <= 3) {
        newVal = newVal.replace(/^(\d{0,3})/, '()');
      } else if (newVal.length <= 6) {
        newVal = newVal.replace(/^(\d{0,3})(\d{0,3})/, '() ()');
      } else if (newVal.length <= 10) {
        newVal = newVal.replace(/^(\d{0,3})(\d{0,3})(\d{0,4})/, '() ()-');
      } else {
        newVal = newVal.substring(0, 10);
        newVal = newVal.replace(/^(\d{0,3})(\d{0,3})(\d{0,4})/, '() ()-');
      }
    }
    return newVal;
  }

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

HTML:

<div class="input-wrap">
  <mat-form-field>
    <mat-label>{{Label}}</mat-label>
    <input #input matInput [attr.maxlength]="MaxLength" [placeholder]="PlaceHolder ? PlaceHolder : ''"
      [readonly]="ReadOnly" [type]="type ? type: 'text'"
      (input)="onInputChange($event)" (blur)="onTouched()">
  </mat-form-field>
</div>