Angular: Iterate over ngForm controls?

Viewed 6975

The goal is when a user clicks a button to go to the next page, prevent the user from continuing and display an error indicator if the page is partially filled out but not valid.

In a template-driven form, I have several container elements using the NgModelGroup attribute to represent a "page" in the form. In my component I want to reference this collection so I can access them with an index. Since I couldn't get this collection (@AJT_82 comments why below), this was my approach:

I made a class to hold info about a page.

export class Page
{
    title: string;
    hasError: boolean;  //should the page display an error indicator
}

and in my component I populate an array, pages: Page[], in ngOnInit

ngOnInit()
{
    this.pages = [{title: "Page 1", hasError: false},
                  {title: "Page 2", hasError: false},
                  {title: "Page 3", hasError: false}]
}

and @DeborahK in her answer gave me

this.form.form.get('pg1')) 

to get the individual ngModelGroup named 'pg' + currentPg+1 (+1 to match the View since arrays starts at 0) that I could then use in a click event that will result in either A)going to the next page or B)setting the hasError property to true and not going to next page.

let p = this.form.form.get("pg"+ (this.currentPg+1));
//if the page is partially filled out but not valid
if(p.invalid && p.dirty)
  this.pages[this.currentPg].hasError = true;
else
{
  //even if it's false, re-false it ;)
  this.pages[this.currentPg].hasError = false;

  //continue to next page. 
  //i.e. this.currentPg++ or this.currentPg--
}

Back in the template, to show an error indicator on either the tab or the page I just check the pages[currentPg].hasError property. Assign "has-error" class on tab element for styling the tab.

<div id="tabs">
  <a *ngFor="let p of pages" 
     [ngClass]="(p.hasError ? ' has-error' : '')"><p>{{p.title}}</p></a>
</div>

<form #f="ngForm">
  <div ngModelGroup="pg1"> <!-- pages[0] -->
    <div id="errorBlock" *ngIf="pages[currentPg].hasError">
      You had an error.
    </div>
    <div>
      <input ngModel/>
      <input ngModel/>
    </div>
    <div>
      <input ngModel/>
      <input ngModel/>
    </div>
  </div>
  <div ngModelGroup="pg2"> <!-- pages[1] -->
    <input ngModel/>
  </div>
</form>

and here's the example's component:

...
currentPg: number = 0;
pages: Page[] = [];
@ViewChild('f') public form: NgForm;

ngOnInit()
{
    this.pages = [{title: "Page 1", hasError: false},
                  {title: "Page 2", hasError: false},
                  {title: "Page 3", hasError: false}]
}

NextPage()
{
  let p = this.form.form.get("pg"+ (this.currentPg+1));
  //if the page is partially filled out but not valid
  if(p.invalid && p.dirty)
    this.pages[this.currentPg].hasError = true;
  else
  {
    //even if it's false, re-false it ;)
    this.pages[this.currentPg].hasError = false;

    //Do navigation logic. 
    //i.e. this.currentPg++ or this.currentPg--
  }
}

Again, a lot of this can go away if there's a way to get the collection of ngModelGroups and use it like an array.

2 Answers
foo( f: NgForm ){
    Object.values(f.controls).forEach( ctl => {
          console.log(ctl);
    } );
}
Related