6

quick question. It's one of those small annoying bugs. I have an Angular reactive form in a lazy-loaded signup module. The code structure is as follows:

TS

get email() {
  return this.myForm.get('email');
}
// This code works from within the component, but not when referenced from within the template.

HTML

<div class="errors" *ngIf="email.errors?.required && email.touched">Must enter email</div>

In the template, the div is never shown since the expression never evaluates to true, even when there are errors present. I've checked in the console.

What's more confusing is that I have no problem using the getter from within the component.ts, in fact, the way I console.log it is by writing console.log(this.email.errors). And That works fine. But not in the template.

My messy solution is to access the errors directly, and that does work:

<div class="errors" *ngIf="myForm.controls.email.errors.required && myForm.controls.email.touched">
  Must enter email
</div>
// This works! As you can see the expression is very long and messy.

Any help appreciated!

George43g
  • 565
  • 10
  • 20
  • Just wondering, have you tried using a function instead, like `getEmail() { return .. }` ? – Murhaf Sousli Jan 09 '20 at 03:02
  • Can you please replicate this issue in stackblitz? Unfortunately, the first case works for me – yurzui Jan 09 '20 at 03:38
  • it's hard for me to replicate it at all - that's part of my confusion. There's another form in my app where the getters work just fine. But in this particular component, for whatever reason, it won't work. I suspect it has something to do with change detection, but I don't understand ng internals enough to really get it. – George43g Jan 10 '20 at 04:15
  • @MurhafSousli, I'll try this, seems to be a good way of diagnosing it. I'll report back shortly. – George43g Jan 10 '20 at 04:18
  • @MurhafSousli it seems that using a function works fine. It's only when it's a getter that it doesnt work. Why would that be? – George43g Jan 10 '20 at 04:48

2 Answers2

2

Do you by any chance use a template reference variable #email in your input field, like: <input #email ...>?

That would explain the problem. I made a Stackblitz which reproduces the error: https://stackblitz.com/edit/angular-qyt7kb

In that example the firstName input has the template reference variable #firstName AND the error div is never shown. The lastName error div is shown correctly.

<form [formGroup]="form">

    <label>
      First Name:
      <input #firstName type="text" formControlName="firstName">
    </label>
    <div *ngIf="firstName.errors?.required && firstName.touched">*FIRST Name is Required</div>

    <label>
      Last Name:
      <input type="text" formControlName="lastName">
    </label>
    <div *ngIf="lastName.errors?.required && lastName.touched">*LAST name is Required</div>

</form>

So it seems that in the template, the template reference variable has precedence over the getter of the same name in the TS file. In the example you can see that the console.log prints the errors correctly just as you said.

Kari F.
  • 1,214
  • 10
  • 16
  • 2
    Thank you! You nailed it! That was the issue and changing the template reference variable fixed the issue!. I need to learn more about how those template variables can be used - I had no idea it can interfere. – George43g Jan 10 '20 at 07:14
0

In order to use Reactive Forms, make sure you're using formGroup and formControlName directives in your template as follows:

<form (ngSubmit)="submit()" [formGroup]="myForm">
    <label for="username">Username</label>
    <input type="text" id="username" formControlName="username">
    <span *ngIf="username.getError('required') &&
              (username.dirty || username.touched)">
      Username is required
    </span>

    <label for="password">Password</label>
    <input type="password" id="password" formControlName="password">
    <span *ngIf="myForm.get('password').getError('required') &&
              (myForm.get('password').dirty || myForm.get('password').touched)">
      Password is required
    </span>

    <input type="submit" value="Submit">
</form>

Then define your FormGroup instance, validators and getter functions:

  myForm: FormGroup = new FormGroup({
    username: new FormControl('', Validators.required),
    password: new FormControl('', Validators.required),
  });

  get username() {
    return this.myForm.get('username');
  }

  get password() {
    return this.myForm.get('password');
  }

After that, you can use the getter function for validation(as username in the template) or use a direct reference with myForm.get('password').getError('required')(as password in the template).

Take a look a login sample applying both ways to access to errors on Reactive Forms in Angular here: https://stackblitz.com/edit/reactive-forms-get-errors

Let me know if that helps!

luixaviles
  • 691
  • 5
  • 8
  • All the elements you described are present - everything else about the form works. It's literally just the getters, that are used to shorted the code in the template, that don't work. The getters work fine from within the TS component, but not when referenced from the .html template.... :( – George43g Jan 10 '20 at 04:16
  • Please see above-chosen answer, that was the correct solution :) – George43g Jan 10 '20 at 08:20
  • How are getters recommended for validation? I thought we should never use functions or getters directly inside a template because it will trigger on every change detection cycle. Put a console.log inside the getter and see how much times it is logged.. But they also do this in the angular docs? – Joestoen Jan 06 '21 at 12:35