DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Related

  • Driving DevOps With Smart, Scalable Testing
  • Unit Testing Large Codebases: Principles, Practices, and C++ Examples
  • AI’s Role in Everyday Development
  • Practical Use of Weak Symbols

Trending

  • GitHub Copilot's New AI Coding Agent Saves Developers Time – And Requires Their Oversight
  • Start Coding With Google Cloud Workstations
  • Why Database Migrations Take Months and How to Speed Them Up
  • The Future of Java and AI: Coding in 2025
  1. DZone
  2. Coding
  3. Frameworks
  4. Writing and Testing Custom Angular Validators: The 'Passwords Matching' Use Case

Writing and Testing Custom Angular Validators: The 'Passwords Matching' Use Case

In this article, we'll teach you how to make the bane of modern man's existence: the 'password does not match' validation field.

By 
Diego Pappalardo user avatar
Diego Pappalardo
·
Jul. 07, 17 · Tutorial
Likes (4)
Comment
Save
Tweet
Share
38.6K Views

Join the DZone community and get the full member experience.

Join For Free

Imagine you are implementing a subscription form and you ask the user to type a password… and then to re-type it just to make sure. You may want to give the user some feedback if he typed a different password the second time, right?

Image title

One way of resolving that is to write a custom validator.

Validate This!

Our validator will be bound to a “Repeat Password” field and observe the original “Password” field: it will get the upper field’s value and compare it to its own (the lower one) to establish if the passwords match or not.

The form that renders such a situation might look a bit like this:

<form #myForm="ngForm">
    ...
    <input name="password" #passwordModel="ngModel" required [(ngModel)]="password">
    <input name="repeatPassword" #repeatPasswordModel="ngModel" required [fieldMatches]="passwordModel" [(ngModel)]="repeatPassword">"
    ...
    <span [hidden]="!repeatPasswordModel?.errors?.fieldMatches">
        The password typed in this field does not match the field above!
    </span>
    ...
</form>

Nothing surprising here: we just make sure we have a reference to the “Password” field’s model because that’s what we will pass as a parameter to our custom validator, which we have named “fieldMatches.”

So how do we define that “fieldMatches” validator? We must declare a directive for that purpose, like this:

import {Directive, Input, OnChanges, SimpleChanges} from "@angular/core";
import {
  AbstractControl, NG_VALIDATORS, NgModel, ValidationErrors, Validator, ValidatorFn,
  Validators
} from "@angular/forms";

@Directive({
  selector: '[fieldMatches]',
  providers: [{
    provide: NG_VALIDATORS,
    useExisting: FieldMatchesValidatorDirective,
    multi: true
  }]
})
export class FieldMatchesValidatorDirective implements Validator, OnChanges {
  @Input() fieldMatches: NgModel;

  private validationFunction = Validators.nullValidator;

  ngOnChanges(changes: SimpleChanges): void {
    let change = changes['fieldMatches'];
    if (change) {
      const otherFieldModel = change.currentValue;
      this.validationFunction = fieldMatchesValidator(otherFieldModel);
    } else {
      this.validationFunction = Validators.nullValidator;
    }
  }

  validate(control: AbstractControl): ValidationErrors | any {
    return this.validationFunction(control);
  }
}

export function fieldMatchesValidator(otherFieldModel: NgModel): ValidatorFn {
  return (control: AbstractControl): ValidationErrors => {
    return control.value === otherFieldModel.value ? null : {'fieldMatches': {match: false}};
  };
}

Let’s go through the sequence of events step by step:

  1. When the form field with fieldMatches is set up, a change event is fired and ngOnChanges() creates the validator function by using our homemade function factory, named fieldMatchesValidator().
  2. What is passed to fieldMatchesValidator() is the NgModel of the other field. This implies that in this implementation the change will be fired only once, essentially to generate our validation function!
  3. Then the user types something in the “Repeat Password” field, therefore the function assigned to this.validateFunction is called and receives the current AbstractControl.
  4. The function assigned to this.validationFunction, if the initialization took place, is the one containing our validation logic and initially provided by our function factory: fieldMatchesValidator().
  5. Our validation logic gets the AbstractControl object from which it can extract the field’s current value. It compares it with the value from the “Password” field’s model.
  6. If the fields are equal, null is returned. That means “all is well, mate!”
  7. If the fields are not equal, an object describing what is wrong is returned: ValidationErrors, which is basically an alias on a map. That object is added to the list of errors carried by the “Repeat Password” field’s NgModel.
  8. So now it is possible for your code to look at the “Repeat Password” NgModel and display something if its errors property carries a fieldMatches issue.

Validate the Validator

Now we could write isolated unit tests to test our validator, but these would only really be useful to verify our validation logic, which amounts to one line of code in this case. Or we could use Angular testing utilities to do something similar to an integration test, and see how our validator interacts with Angular and (most importantly) with a template.

We’ll go for the second option. For that we need two things:

  • A fake template that will expose our validator.
  • TestBed: a testing utility that allows us to set up an Angular testing module.

Brace yourself, there goes the code:

import {CommonModule} from "@angular/common";
import {FormsModule} from "@angular/forms";
import {Component} from "@angular/core";
import {async, ComponentFixture, ComponentFixtureAutoDetect, TestBed} from "@angular/core/testing";
import {By} from "@angular/platform-browser";

describe('FieldMatchesValidatorDirective', () => {
  let component: TestComponent;
  let fixture: ComponentFixture<TestComponent>;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      imports: [CommonModule, FormsModule],
      declarations: [TestComponent],
      providers: [
        { provide: ComponentFixtureAutoDetect, useValue: true },
      ]
    }).compileComponents();
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(TestComponent);
    component = fixture.componentInstance;
  });

  it('should invalidate two fields that do not match', async(() => {
    component.field1 = '12345678901234';
    component.field2 = '12345678999999';

    fixture.detectChanges();
    fixture.whenStable().then(() => {
      fixture.detectChanges();

      let field2Model = fixture.debugElement.query(By.css('input[name=field2]')).references['field2Model'];

      expect(field2Model.valid).toBe(false);
    });
  }));
});

@Component({
  template: '<form #form1="ngForm">' +
            '<input name="field1" #field1Model="ngModel" [(ngModel)]="field1">' +
            '<input name="field2" #field2Model="ngModel" [fieldMatches]="field1Model" [(ngModel)]="field2">' +
            '</form>'
})
class TestComponent {
  field1: string;
  field2: string;
}

The component at the very bottom (named… TestComponent, how original) is merely a component we set up for our test. It defines an in-line template, which declares our validator on one of two fields in a dummy form. It also exposes two properties that are bound to the dummy form’s fields.

That’s the component for which a fixture is returned when calling TestBed.createComponent().
When running, our test uses that fixture to set up the fields’ values using the fixture’s exposed component instance. It waits for all eventual pending asynchronous activities to end, fetches the second field’s reference to the model, from the fixture (always from the fixture, remember!), and tests if its validation status matches what we expect.

Conclusion

And essentially that’s all there is to it!

Now for a little trivia: if you implement the validator as described above, you will notice that in a specific situation you will not get the expected result. Which one, and why? I’ll let you ponder this.

This post is a shorter version of the one published on my blog. If you need a little more explanations or details feel free to check it here.

Until then,

Cheers!

AngularJS Use case unit test

Published at DZone with permission of Diego Pappalardo. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Driving DevOps With Smart, Scalable Testing
  • Unit Testing Large Codebases: Principles, Practices, and C++ Examples
  • AI’s Role in Everyday Development
  • Practical Use of Weak Symbols

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends: