Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Radio button validation using template driven forms and NgFor in Angular 2

Could someone please explain me why when trying to validate a radio button without using NgFor for the options works:

@Component({
  selector: 'rio-app',
  template: `
    <form #myForm="ngForm">

      <label>
        Male: 
        <input type="radio" name="gender" value="Male" 
          ngModel required #gender="ngModel"
        />
      </label>

      <label>
        Female: 
        <input type="radio" name="gender" value="Female" 
          ngModel required #gender="ngModel"
        />
      </label>

      <div [hidden]="!gender.hasError('required')">
        The gender is required
      </div>

    </form>
  `
})
export class AppComponent {}

Plunker

But if I try to do the same thing using NgFor it fails:

@Component({
  selector: 'rio-app',
  template: `
    <form #myForm="ngForm">

      <label *ngFor="let gender of genders">
        {{ gender }}: 
        <input type="radio" name="gender" [value]="gender" 
          ngModel required #myGender="ngModel"
        />
      </label>

      <div [hidden]="!myGender.hasError('required')">
        The gender is required
      </div>

    </form>
  `
})
export class AppComponent {
  genders = ['Male', 'Female']
}

Plunker

I'm getting an error message in the browser's console that says:

Cannot read property 'hasError' of undefined

Any ideas?

like image 655
David Barreto Avatar asked Jan 17 '17 02:01

David Barreto


2 Answers

So I ended up opening an issue in the Angular repo and one person (@alexzuza) gave me a good answer that I want to share with you for future references:

import { Component } from '@angular/core';

@Component({
  selector: 'rio-app',
  template: `
    <form #myForm="ngForm">

      <input type="text" />

      <ng-container *ngFor="let gender of genders; let isLast = last">

        <label>
          {{ gender }}: 
          <input type="radio" name="gender" [value]="gender" 
            ngModel required #myGender="ngModel"
          />
        </label>

        <div *ngIf="isLast" [hidden]="!myGender.hasError('required')">
          The gender is required
        </div>

      </ng-container>

    </form>
  `
})
export class AppComponent {
  genders = ['Male', 'Female']
}

To my understanding, the trick is to do everything inside of the NgFor using the <ng-container> because every variable created inside the loop will have block scope.

Here's a working plunker

like image 20
David Barreto Avatar answered Sep 23 '22 05:09

David Barreto


Looking at that second plunker it seems that the template reference variable #myGender defined within the *ngFor isn't visible outside i.e. in that error message div.

But you do have access at that level to #myForm though, so you can use that to check for errors on the gender FormControl.

<div [hidden]="!myForm.controls.gender?.hasError('required')">
    The gender is required
</div>

From the Angular doco I knew that the gender variable defined in the *ngFor definition wouldn't be visible but I wasn't sure about template variables within that block. Someone else might provide us with a better explanation of that.

like image 50
Garth Mason Avatar answered Sep 24 '22 05:09

Garth Mason