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

Encountering CORS Error: Challenge in sending post requests with NodeJs and Angular

Whenever I attempt to make a post request, I encounter the following error message: Access to XMLHttpRequest at 'http://localhost:3002/api/products/checkout' from origin 'http://localhost:4200' has been blocked by CORS policy: Request ...

Utilizing Angular signals to facilitate data sharing among child components

I have a main component X with two sub-components Y and Z. I am experimenting with signals in Angular 17. My query is how can I pass the value of a signal from sub-component Y to sub-component Z? I have attempted the following approach which works initial ...

Redirect users in Angular 9 when query parameter is missing

When a user clicks a link from an email, they should be directed to a specific route only if the URL contains a valid query parameter. If the parameter is missing, I want to redirect them to a 404 page not found component as shown below. this.route.queryP ...

Troubleshooting the display of API-generated lists in Angular 8

I am encountering an issue in Angular 8 when trying to display my list on a page. Below is the code from my proposal-component.ts file: import { Component, OnInit, Input } from "@angular/core"; import { ActivatedRoute, Params } from "@angular/router"; imp ...

Why am I getting a null value for my Array when I should be expecting an Object instead?

I have been attempting to create an Array that contains objects for a project, but I keep encountering an error message stating: "this.allgmArray is undefined". However, it is not actually undefined: allgmArray: DialogBook[]; [...] load(){ for(var i ...

Leverage Redux in Angular for account balance management

As I work on creating an app in Angular where users can make purchases and have the price deducted from their account balance, I've been delving into Redux. The scenario involves individual user accounts with independent balances and no interaction be ...

Create a dynamically updating list using React's TypeScript rendering at regular intervals

My goal is to create a game where objects fall from the top of the screen, and when clicked, they disappear and increase the score. However, I am facing an issue where the items are not visible on the screen. I have implemented the use of setInterval to d ...

Tips for mocking the router.navigate function in Jest

As a newcomer to unit testing with Jest in Angular, I find myself facing a challenge when it comes to testing components that utilize the this.router.navigate() method. Previously, I used Jasmine for testing and followed these steps: import { Router } from ...

What are the steps to globalize the ng-bootstrap datepicker?

For my current project, I am utilizing the ng-bootstrap datePicker component. The demo for my simple datePicker widget can be found here. However, I am now seeking to internationalize it by setting it to use the Russian language. Any assistance with this ...

When working with Angular and evaluating code, I encounter an issue related to the environment that

Here is my code snippet: let var1="environment.test"; console.log(eval(var1)); But when I run it, an error occurs: ERROR ReferenceError: environment is not defined However, if I modify the code to be like this: console.log(environment.test); it works ...

Tips for preventing the occurrence of a final empty line in Deno's TextLineStream

I executed this snippet of code: import { TextLineStream } from "https://deno.land/<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="7201061632425c4341445c42">[email protected]</a>/streams/mod.ts"; const cm ...

Tips for triggering a function each time a view is shown

I am currently developing an inappbrowser application that involves communication between the webview and the app. Below is a snippet of my code : import { Component } from '@angular/core'; import { NavController, Platform } from 'ionic-an ...

Implementing CORS headers in both Angular 9 frontend and Django 3 backend for seamless communication

Struggling to properly configure a front-end app in Angular 9 that consumes a RESTful backend web service. I have my Angular development server running on a remote server with a static IP using ng serve --host 0.0.0.0 --port 80 so it can be accessed extern ...

Mastering the Art of Flex Layout Grids

Here is a preview of how my grid is currently formatted: https://i.stack.imgur.com/SBChV.png The current code looks like this: <div fxLayout="row wrap"> <img class="component-logo" fxFlex="1 1 c ...

Ionic app: refresher functionality works in browser but not on iOS home screen app

I am currently developing a Progressive Web App (PWA) using Ionic, and I have implemented an ion-refresher in the app: <ion-content> <ion-refresher slot="fixed" (ionRefresh)="refresh()"> <ion-refresher-content pullingIcon="lines">& ...

The Angular template loads and renders even before the dynamic data is fetched

I'm encountering a frustrating issue where the page loads before the data is retrieved. When I log the names in $(document).ready(), everything appears correct without any errors in the console. However, the displayed html remains empty and only shows ...

Transfer a reference from one list to another within the Redux store

Within my store, I have various lists that contain unique identifiers referencing normalized entities. The structure is typically as follows: { list1: [ 1 ], list2: [], //other lists entities: { 1:{data}, ... } } Users have the abil ...

Leveraging Angular for Remote Configuration Management

How is everything going with you? I'm attempting to retrieve a configuration that I previously set up in Firebase's remote config using my Angular 15 application. The specific configuration is called "AllowedUsers." Here is the image of th ...

Error message in Angular: Unable to locate a differ that supports the object '[object Object]' of type 'object.' NgFor is only able to bind to iterables like Arrays

When making an API call in my Angular project, I receive the following JSON response: { "data": { "success": true, "historical": true, "date": "2022-01-01", "base": "MXN&quo ...

What is the process of programmatically sorting a column in a Material UI DataGrid?

Hey there! I'm currently working on a DataGrid that has a column with a custom header, specifically a Select option. My goal is to have the column sorted in descending order every time a user selects an option from the dropdown menu. renderHeader: (pa ...