Custom Angular Form Password Component
Tags: angular, tutorial, typescript
Published: 2022-03-08 23:28:02

In this post we are going to create a custom component which is designed for Reactive Forms and with a few tweaks can be fully functional for Template Driven Forms within Angular. The component will wrap the Angular Material Form Field to simplify the styling of the component. We will implement the following requirements for this component.

* Password component which can be linked to a form;
* Password visibility to show / hide password in plain text;
* Perform form field validations and display error messages;
* Show as required;

Check out this [Stackblitz](https://lss-angular-password-component.stackblitz.io/) to see a full working example, and this [Github](https://github.com/lloydmi/angular-custom-password-component) repo for the full code base being built out below.

See the original article on my website: [Custom Angular Form Password Component](https://lloydsoftwaresolutions.com/custom-angular-form-password-component/)

## Initializing the project and component

**_Step 1: Create Project_**

```bash
ng new angular-custom-password-component --style=scss
```

Note that the above will set-up the project to use scss stylesheets for the components and the application, if you chose you can leave off the `style=scss` to keep the standard css stylesheets.

**_Step 2: Create Component_**

```bash
ng generate component password-input
```

Now that we’ve created the project and the base component within the project, let’s start building out the details of the component. We’ll go over the implementation by section to show more of what each part of the code is doing.

## Implementing ControlValueAccessor Interface

**_Step 3: Update Component to implement the Control Value Accessor_**

```typescript
import { Component } from '@angular/core';
import { ControlValueAccessor } from '@angular/forms';

@Component({
  selector: 'app-password-input',
  templateUrl: './password-input.component.html',
  styleUrls: ['./password-input.component.scss']
})
export class PasswordInputComponent implements ControlValueAccessor {

  disabled = false;
  onChange = (value) => {};
  onTouched = () => {};
  touched = false;
  value: string = null;

  constructor() { }

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

  registerOnTouched(fn: any) {
    this.onTouched = fn;
  }

  setDisabledState(isDisabled: boolean) {
    this.disabled = isDisabled;
  }

  writeValue(obj: any): void {
    this.value = obj;
  }
}
```

* registerOnChange – registers the callback function within the component when the control’s value is changed within the UI and stores it in the onChange function variable on the component.
* registerOnTouched – registers the callback function which will update the form model on blur and stores it in the onTouched function variable on the component.
* setDisabledState – called by the forms API when changing the status to/from disabled and stores it in the disabled property of the component.
* writeValue – writes a new value to the element and stores it within the value property of the component.

**_Step 4: Register the component as a Value Access_**

Most of the components out there will use the `NG_VALUE_ACCESSOR` provider which will do some of the auto-wiring for you. However, this component has the need for being able to access the control itself as we’ll see later when getting to the validation portion. To accomplish this, we are going to inject the `ngControl` into the constructor. Update the constructor to the following:

```typescript
constructor(@Optional() @Self() public ngControl: NgControl) {
    if (ngControl !== null) {
        ngControl.valueAccessor = this;
    }
}
```

The `ngControl` gets injected when the component is created by Angular’s Dependency Injection but we need to make sure that we are registering this component as the `valueAccessor`. This gives the form API access to the ControlValueAccessor that was implemented.

**_Step 5: Link the HTML to the component_**

Let’s start hooking up the work we’ve done to the HTML of the component. As I said in the beginning, this is going to end up being a wrapper around Angular Material. Set the HTML to the following:

```html
<div class="password-input-wrapper">
    <mat-form-field>
        <mat-label>Password</mat-label>
        <input matInput [disabled]="disabled" [value]="value" />
    </mat-form-field>
</div>

```
Now, the value and the disabled attributes are hooked up. So if you initialize a form with a value and a disabled state, then you’ll see that the value is passed down to this component and shows up in the input and/or disables it.

As of now, if you change the value it doesn’t update the parent form. Even though it’s hooked up, it’s only pushing information down from the parent form. We need to implement the two way binding. But first, let’s start building out the parent form to show the functionality in action.

**_Step 6: Create parent form_**

```html
<div class="ui-container">
    <form [formGroup]="formGroup">
        <app-password-input formControlName="password"></app-password-input>
    </form>
</div>
```

```typescript
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms';

@Component({
    selector: 'app-root',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit{
    formGroup: FormGroup = null;

    constructor(private _formBuilder: FormBuilder) {

    }

    ngOnInit() {
        this.formGroup = this._formBuilder.group({
            password: this._formBuilder.control(null)
        });
    }
}
```

Here it’s a very basic form, with just the initialization of the password component with a null value and setting the form control name to link the component. An issue with the way the form is currently set-up is that you can’t see anything happen. So let’s update the HTML to following:

```html
<div class="ui-container">
    <form [formGroup]="formGroup">
        <app-password-input formControlName="password"></app-password-input>
    </form>
    <div>
        <span>Form values</span>
        <pre>{{ formGroup.value | json}}</pre>
    </div>
</div>
```

**_Step 7: Listening for changes_**

First, enter the listener into the password component.

```typescript
onInput($event: any): void {
    this.value = $event.currentTarget.value;
    this.onChange(this.value);
}
```

Then hook it up to the HTML with the input event binding.

```html
<input matInput [disabled]="disabled" [value]="value" (input)="onInput($event)" />
```

Now, you can see updates in the component are passed into the parent form and available to be used.

## Implementing Validations
At this point, you have a functional component which you can hook up to a Reactive Form. Depending on your needs, this may be enough but from my experience developing enterprise level components we need to at least implement validations. In order to do that, we have a couple more things to wire up. The first being the `onTouched` event. The material component won’t show any mat-errors nor will it highlight the field as invalid unless the component has been touched.

**_Step 8: Register onTouched events_**

Technically, we registered the `onTouch` event earlier into this post. However, it’s just registered, we aren’t actually using it. It’s pretty simple to wire up, just add the event that you want to trigger it such as blur or focus out. In this case, we are using focus out.

```html
<input matInput [disabled]="disabled" [value]="value" (input)="onInput($event)" (focusout)="onFocusOut()" />
```

Then the corresponding method on the component.

```typescript
onFocusOut(): void {
    this.onTouched();
}
```
Now it’s time to diverge from the normal a little bit, and while I was building out this component for my own application and this posting, there were still a few things that my component wasn’t doing which I wanted it to do.

* Mark the field with asterisks when providing the required validator in the parent form;
* Mark the field red when it’s invalid;
* Show mat-error messages;

As I mentioned earlier, I had injected the `ngControl` because of an issue I encountered with validations. It was marking the field with the asterisks. After doing some digging in the `mat-input` / `mat-form-field` components from angular I discovered that I could access the control and check to see if it had the required validator associated with it. I do this through a getter and setter of the required attribute, this way it supports template-driven design and reactive-forms. The template-driven comes from the input decorator itself which will store and override the missing validator. Then for reactive-forms I tap into the control and check if the validator exists.

```typescript
get required(): boolean {
    return this._required ?? this.ngControl?.control?.hasValidator(Validators.required) ?? false;
}

@Input()
set required(value: boolean) {
    this._required = value;
}
```

And then link it up with the HTML.

```html
<input matInput [disabled]="disabled" [value]="value" (input)="onInput($event)" (focusout)="onFocusOut()" [required]="required" />
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u2lqhl2e4v9607wdedka.png)

In order to meet the last two aspects of my requirement, I had to implement an `errorStateMatcher` in addition with notifying the `mat-input` to update its error state.

**_Step 9: Register Error State Matcher_**

Update the component so that it implements the ErrorStateMatcher by adding the interface to the implements collection.

```typescript
export class PasswordInputComponent implements ControlValueAccessor, ErrorStateMatcher {
}
```

Then implement the interface by implementing the `isErrorState` method.

```typescript
isErrorState(control: AbstractControl | null, form: FormGroupDirective | NgForm | null): boolean {
    return this.touched && (this.ngControl?.control?.invalid ?? false);
}
```

Following along with standard `mat-form-field` implementations, we are going to make sure that the field has been touched and then, again, access the control itself on the `ngControl` to make sure it is invalid.

Next update the HTML to register it with the input control.

```html
<input matInput
       [disabled]="disabled"
       [errorStateMatcher]="matcher"
       (focusout)="onFocusOut()"
       (input)="onInput($event)"
       [required]="required"
       [value]="value"
/>
```

**_Step 10: Notify MatInput for Error State Changes_**

The final piece to getting the validations and `mat-errors` to show up within custom control component, as if they would with an implementation directly associated to the form. We need to tell mat-input to update its error state, but first we need to be able to access it. We’ll do this using the `@ViewChild` decorator to put it into the component.

```typescript
@ViewChild(MatInput)
matInput: MatInput;
```

Then, depending on how quickly you want the error state to be updated you can add the call to the onInput method. I chose to do it on the `focusout` call to make it react more closely with angular material.

```typescript
onFocusOut(): void {
    this.onTouched();
    this.matInput.updateErrorState();
}
```

The last and final piece would be to add the mat-errors to the HTML component. Unfortunately, I tried many different ways to inject the messages from the parent down into the component but was unable to find an acceptable solution. So adding errors such as this will allow them to show when the control has the validation message.

```html
<mat-error *ngIf="ngControl.hasError('required')">Password is a required field.</mat-error>
```

## Enhanced Features

**_Step 11: Password Visibility Toggle_**

It’s pretty standard now, that on a password field you have the option to toggle the password formatting of the input into plain text. So let’s add one to our component.

In the HTML add the icon we’ll use as the toggle.

```html
<mat-icon matSuffix (click)="onVisibilityClick($event)">{{ icon }}</mat-icon>
```

The `onVisibilityClick` implementation:

```typescript
onVisibilityClick($event): void {
    if (this._visible) {
        this.icon = 'visibility_off';
        this.type = 'password';
    } else {
        this.icon = 'visibility';
        this.type = 'text';
    }

    // Invert the value.
    this._visible = !this._visible;

    $event.stopPropagation();
}
```

We need to make sure that we are toggling the icon which will be used as feedback to the user to indicate which mode the input is in. We also need to change the type of the input to convert it from a password input to plain text and vice versa.

One thing that I noticed while implementing the toggle, (especially with the floating label from Angular Material) is that when you click on the toggle the label will jump around as the input regains focus after the click event propagates up the chain. To resolve that I passed in the $event object and called the `stopPropagation` method to prevent the bubbling up of the click event.

**_Step 12: Dynamic label_**

Unless you want to call every field password every time you want to use this component, you’ll want to make sure that you can provide a label from any parent component.

Update the HTML to:

```html
<mat-label>{{ label }}</mat-label>
```

Add the input to the component so it can be declared:

```typescript
@Input()
label: string = null;
```

**_Step 13: Adding error validations_**

The final portion of the component is displaying validation errors underneath the field when there are validation messages within the form. We are going to hard code a specific message for the required error to enhance the earlier feature we implemented. We are also going to allow for a custom input of an error message and the name of the corresponding control. This way, in the parent component you are able to provide custom validators and then have the message displayed as an error.

```html
<mat-error *ngIf="ngControl.hasError('required')">{{ label }} is a required field.</mat-error>
<mat-error *ngIf="ngControl.hasError(customErrorName)">{{ customErrorMessage }}</mat-error>
```

We are re-using the dynamic label within the required message to link the elements together and we are checking to see for the custom error. Here again, you can see how we are using the `ngControl` that was injected earlier.

Don’t forget to define the inputs for the custom error message.

```typescript
@Input()
customErrorMessage: string = null;

@Input()
customErrorName: string = null;
```
And that's it. You now have a custom Password component which can be used in reactive forms.

## Using the Component
The component itself is pretty easy to use once it’s set-up. You just need to set-up your form group, link the controls to the component and provide any custom error messages you may want. As I mentioned earlier in this article, I’m display the errors and the values of form to be able to see the changes.

The HTML of the parent form:

```html
<div class="ui-container">
    <div class="ui-input-container">
        <form [formGroup]="formGroup">
            <div>
                <app-password-input
                    formControlName="password"
                    label="Password"
                    customErrorName="passwordStrength"
                    [customErrorMessage]="invalidPasswordMessage"></app-password-input>
            </div>
            <div>
                <app-password-input
                    formControlName="confirm"
                    label="Confirm Password"
                    customErrorName="passwordMismatch"
                    [customErrorMessage]="confirmPasswordMessage"></app-password-input>
            </div>
        </form>
    </div>
    <div>
        <span>Form values</span>
        <pre>{{ formGroup.value | json}}</pre>
    </div>
    <div>
        <span>Form Errors</span>
        <pre>{{ formGroup.get('password').errors | json }}</pre>
    </div>
</div>
```

And the parent component:

```typescript
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validator, Validators } from '@angular/forms';
import { passwordStrengthValidator } from './validators/password-strength-validator';
import { confirmPasswordValidator } from './validators/password-match-validator';

@Component({
    selector: 'app-root',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {
    formGroup: FormGroup = null;

    confirmPasswordMessage = 'The passwords do not match.';
    invalidPasswordMessage = 'Must contain at least 1 number, 1 uppercase letter, 1 lowercase letter and at least 8 characters.';

    constructor(private _formBuilder: FormBuilder) {

    }

    ngOnInit() {
        const passwordControl = this._formBuilder.control({
            disabled: false,
            value: null
        }, [Validators.required, Validators.minLength(8), passwordStrengthValidator()]);

        const confirmPasswordControl = this._formBuilder.control({
            disabled: false,
            value: null
        }, [Validators.required, Validators.minLength(8), confirmPasswordValidator(passwordControl)]);

        this.formGroup = this._formBuilder.group({
            confirm: confirmPasswordControl,
            password: passwordControl
        });
    }
}
```

Thanks for taking the time to read the article and I hope that it helped you out.

Just as a reminder, you can see a full working example [Stackblitz](https://lss-angular-password-component.stackblitz.io/) and the code itself in [Github](https://github.com/lloydmi/angular-custom-password-component).