0

I am pretty sure this is something I have done wrong and could do with another pair of eyes to point out the no doubt obvious error in my code.

I have a component that is inside a mat-tab and is lazy loaded. In the component I have a field in a reactive form that is created as below.

I have defined an email validator pattern in environment.ts

export const environment = {
  ...
  validators: {
    // tslint:disable-next-line:max-line-length
    email: /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/gi
  }
}

The form is defined here in the component constructor:

this.form = this.fb.group({
  ...
  'primary_contact': this.fb.group({
    'name': [''],
    'email': ['', { validators: [Validators.required, Validators.pattern(environment.validators.email)], updateOn: 'blur' }],
    'phone': ['']
  }),
  ...
});

And the form is populated in ngOnInit from an input variable:

@Input() tenant: Tenant;

ngOnInit() {
  this.form.patchValue({
    ...this.tenant
  });
  this.form.updateValueAndValidity();
}

And finally in the template I have the following:

<div fxFlex fxLayout="row wrap" fxLayout.xs="column wrap" fxLayoutGap="24px grid" formGroupName="primary_contact”>
  ...
  <div fxFlex="50">
    <mat-form-field class="full-width">
      <mat-label>Primary Contact Phone</mat-label>
      <input matInput type="tel" formControlName="phone" />
    </mat-form-field>
  </div>
  ...
</div>
<mat-divider></mat-divider>
<div class="form-actions">
  <button mat-flat-button color="primary" [disabled]="form.invalid" (click)="onSaveChanges()">Save changes</button>
  <a mat-button [routerLink]="['../../']">Cancel</a>
</div>

Now to my problem, when the component loads the form can be either valid or invalid and switching between the two tabs the field will switch between a valid/invalid state.

Can anyone see what I am doing wrong please?

I have a stackblitz here which replicates the problem I am seeing.

Updated

So after taking a look at the Angular email validator and stripping my example back, I found the following which seems to indicate a problem with the regex itself. See the two snippets below, if you run them using my regex and the second one with the regex from the angular validators you get different results

Using regex from above

const re1 = new RegExp(/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/gi);
for (let i = 0; i < 10; i++) {
  console.log(re1.test('test@gmail.com'));
}

Using regex from Angular email validator

const re2 = new RegExp(/^(?=.{1,254}$)(?=.{1,64}@)[-!#$%&'*+/0-9=?A-Z^_`a-z{|}~]+(\.[-!#$%&'*+/0-9=?A-Z^_`a-z{|}~]+)*@[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?(\.[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?)*$/);
for (let i = 0; i < 10; i++) {
  console.log(re2.test('test@gmail.com'));
}

So to update the question, why does my regular expression test alternate between valid and invalid?

Neil Stevens
  • 3,534
  • 6
  • 42
  • 71
  • There's a built-in validator for email: https://angular.io/api/forms/Validators – Brandon Taylor Jul 03 '18 at 11:24
  • @Brandon (nods) but that only seems to validate that you have an `@` in your email – Neil Stevens Jul 03 '18 at 11:28
  • Here's the regex it uses: https://github.com/angular/angular/blob/6.0.7/packages/forms/src/validators.ts#L57 – Brandon Taylor Jul 03 '18 at 11:30
  • @Brandon have update with some additional findings, what I have noticed with the email validator is that it does not validate the user has a correct email address (e.g. something@somewhere is apparently valid). This is why I am using a pattern validator – Neil Stevens Jul 03 '18 at 11:48
  • Reomove the `g` (global) flag. – Toto Jul 03 '18 at 12:02
  • @Toto you have to be shitting me, removed the `g` flag and it now works as expected – Neil Stevens Jul 03 '18 at 12:11
  • I'm opening a bug on that validator :) – Brandon Taylor Jul 03 '18 at 12:13
  • Looks like the Angular validator is just one character off. The final capture group should not be matched zero or more times using `?`, but rather should be matched one or more times using `+`. See: https://regex101.com/r/FaAE7x/1 – Brandon Taylor Jul 03 '18 at 12:24
  • @Brandon could you let me have a link to the bug as would like to comment as well – Neil Stevens Jul 03 '18 at 12:25
  • This looks like a pretty easy fix. I want to write a few tests on my side first, but will open a PR for them and link it back here for you. – Brandon Taylor Jul 03 '18 at 12:26
  • As per the linked SO above, there is mention in there that using `re.test` with the `g` flag just increments the `re.lastIndex`, may be worth adding `re.lastIndex = 0` to reset the `lastIndex` between calls or look at using `match` instead when the `g` flag is used – Neil Stevens Jul 03 '18 at 12:34
  • @NeilStevens PR: https://github.com/angular/angular/pull/24743 – Brandon Taylor Jul 03 '18 at 14:09
  • Well that got shot down fast. Apparently `someone@somewhere` is valid according to https://stackoverflow.com/questions/20573488/why-does-html5-form-validation-allow-emails-without-a-dot. I would think the most common use case when requesting an email is that a domain is present. – Brandon Taylor Jul 03 '18 at 14:27
  • Well, you learn something new everyday – Neil Stevens Jul 04 '18 at 08:16

0 Answers0