Build an Angular wrapper component for the phone textbox functionality

Looking to transform the Phone Mask solution below into an Angular component. Does anyone have a method to accomplish this? * Any solution that results in a similar component for a Phone textbox will suffice.

Mask for an Input to allow phone numbers?

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

I attempted to copy the code into the component below, but I'm encountering errors:

  • The phonebox allows text to exceed 10 characters.

  • During debugging, when deleting all characters, a character value remains.

The initial answer uses a directive and only functions with form control. The objective is to create a custom company textbox component with its own styling, inputs, etc.

At the end, we make reference to the StackBlitz code.

Typescript:

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;
  ...

</code></pre>

<p><strong>HTML:</strong></p>

<pre><code><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>

Answer №1

Utilizing a Customized StackBlitz

TypeScript:

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})/, '($1)');
      } else if (newVal.length <= 6) {
        newVal = newVal.replace(/^(\d{0,3})(\d{0,3})/, '($1) ($2)');
      } else if (newVal.length <= 10) {
        newVal = newVal.replace(/^(\d{0,3})(\d{0,3})(\d{0,4})/, '($1) ($2)-$3');
      } else {
        newVal = newVal.substring(0, 10);
        newVal = newVal.replace(/^(\d{0,3})(\d{0,3})(\d{0,4})/, '($1) ($2)-$3');
      }
    }
    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>

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Obtain varied results from the Knockout module

In my application, I am utilizing Knockout and Typescript. One of the classes in my code is as follows: class Address { State :string; ZIP: string; Street: string; } I want to create a component that will handle the updating of an object of ...

The error message "ngx-contextmenu - The function connectedTo() is not a valid function for this.overlay

I recently updated my angular version to 13 and also upgraded ngx-contextmenu to version 5.4.0. However, after the upgrade, I encountered an issue where the context menu was not visible and throwing an error as shown in this screenshot. To resolve the pr ...

Place a new button at the bottom of the react-bootstrap-typeahead dropdown menu for additional functionality

Currently, I have successfully implemented the React Bootstrap Typeahead with the desired options which is a good start. Now, my next challenge is to integrate a custom button at the end of the dropdown list for performing a specific action that is not ne ...

Guide to validating multiple form controls simultaneously in Angular 2

This is the primary form group: this.mainForm = this.formBuilder.group({ productType1: new FormArray([], CustomValidators.minSelectedCheckboxes()), productType2: new FormArray([],CustomValidators.minSelectedCheckboxes()), ...

Tips for preventing circular dependencies when using combineSlices in Redux-toolkit

When utilizing combineSlices with createAsyncThunk condition, I find it challenging to avoid circular dependency. My store initiation thunk looks like this: thunk.ts export const initiateFx = createAsyncThunk< InitiatePayload, string, { state: R ...

Adding *ngIf dynamically within a directive allows for conditional rendering based on certain parameters or

Is there a way to dynamically include an *ngIf on an element that is decorated with an attribute directive? I decided to test this with a simple experiment: @Directive({ selector: '[lhUserHasRights]' }) export class UserHasRightsDirective i ...

Angular 2 and Its Multidimensional Arrays

I'm having some trouble understanding Arrays in Typescript ( Angular 2 ). I am looking to create a specific array to send to my API. array = [ cadSocios => true, name => ['name1', 'name2'], part => ['part1', &ap ...

The SonarTsPlugin report is coming back clean with no issues found in the Typescript files

In my current project, I am attempting to analyze an Angular application using SonarQube. This particular project consists of a mix of JavaScript and TypeScript files. During the Sonar analysis process, I have noticed that while issues are being generated ...

Wait for the response from a post request in Angular and retrieve the value once it becomes accessible

In my current method, I am sending data to the backend and receiving information about any mutations that occurred (specifically, how many new rows were added to the database). However, when using this method in its current state, the response holds the de ...

Master the Art of Crafting Unique URLs

I am developing a web page that requires me to call two queries, each requiring an ID. However, I'm unsure of the best way to pass these IDs in the URL. For instance, one query is for books and the other is for authors. Currently, I have considered tw ...

Executing Promises in TypeScript Sequentially

I have a collection of doc objects and I need to send an API request for each doc id in the list, executing the requests in a sequential manner. In my Typescript code, I am using Promise.all and Promise.allSelected to achieve this. [ { "doc_id&q ...

Is it required to double click checkboxes to uncheck them?

My checkboxes require 2 clicks to be unchecked, even though one click is enough to check them. This issue occurs in all 7 checkboxes and there is no onchange() function or similar in TS. import { Component, OnInit, Inject } from '@angular/core&apos ...

Guide on creating a 4-point perspective transform with HTML5 canvas and three.js

First off, here's a visual representation of my objective: https://i.stack.imgur.com/5Uo1h.png (Credit for the photo: ) The concise question How can I use HTML5 video & canvas to execute a 4-point perspective transform in order to display only ...

Updating the reference path in the index.html file during Angular 6 build process

When developing an Angular 6 application, the scripts and CSS files are automatically generated with hashed values at the end of their names. I am wondering if it is possible to update the links to these files in the index.html file. Currently, they point ...

Angular 8: ISSUE TypeError: Unable to access the 'invalid' property of an undefined variable

Can someone please explain the meaning of this error message? I'm new to Angular and currently using Angular 8. This error is appearing on my console. ERROR TypeError: Cannot read property 'invalid' of undefined at Object.eval [as updat ...

Verifying Angular Universal Using JSON Web Tokens

I have a project in Angular 10 Universal where I am using JWT obtained from localhost to verify requests for restricted routes. Currently, I am utilizing the following package for authentication: https://www.npmjs.com/package/@auth0/angular-jwt The issue ...

Encountering an XHR error when using a systemjs module in TypeScript

Error: GET http://localhost:63342/Dog.js 404 (Not Found) XHR error (404 Not Found) loading http://localhost:63342/Dog.js <br/><br/>Below is the script in my index.html file. ...

Error: Select dropdown placeholder malfunctioning

Even after trying numerous solutions from various forums, I am unable to make the placeholder display in my code. Maybe a fresh perspective can help spot what I might be missing. view image here I have experimented with using "" as the value, as ...

LightWeightChart: How do I incorporate a chart that resembles this style?

Is it possible to create a chart using lightweight-chart that includes two graphs sharing the same x-axis but with different values on the y-axis? Essentially, I want to have two panes in a single chart. Does anyone have suggestions or tips on how this c ...

Changing the output of a service by using spyOn: A step-by-step guide

There is a specific service available: export class CounterService { random: any = { value: 123, date: 456, }; getRandom() { return this.random; } } A certain component utilizes this service. Within this component, the variable &apos ...