Angular:如何迭代ngForm控件?

6
当用户点击按钮进入下一页时,如果页面部分填写但不合法,则阻止用户继续并显示错误指示器。
在模板驱动表单中,我有几个容器元素使用 NgModelGroup 属性来表示表单中的“页面”。在我的组件中,我想引用这个集合,以便我可以通过索引访问它们。由于我无法获取这个集合(@AJT_82 在下面评论了原因),这是我的方法:
我创建了一个类来保存关于页面的信息。
export class Page
{
    title: string;
    hasError: boolean;  //should the page display an error indicator
}

在我的组件中,我在ngOnInit中填充一个数组pages: Page[]


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

在她的回答中,@DeborahK 给了我

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

为了得到名为“pg”+当前页码加1(加1以匹配视图,因为数组从0开始)的单独的ngModelGroup,我可以在点击事件中使用它,该事件将导致A)进入下一页或B)将hasError属性设置为true并不进入下一页。
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--
}

回到模板,要在标签或页面上显示错误指示器,我只需要检查pages[currentPg].hasError属性。为了样式化标签,给标签元素分配“has-error”类。
<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>

这是示例组件:

...
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--
  }
}

如果有一种方法可以获取ngModelGroups的集合并像数组一样使用它,那么很多问题都可以解决。


@ViewChild('f') 是什么?this.tabs 是什么? - Max Koretskyi
你没有在使用ViewChild的表单中附加引用。 - Rahul Singh
1
但是你可以看到我想要实现什么:不,我看不到。请解释一下你想要实现什么。 - JB Nizet
抱歉,我忘记删除带有“this.tabs”的行。此外,Viewchild引用是一个打字错误。我已经修复了它。 - slanden
1
没有问题表单控件,但是尝试创建一个表单组的数组会得到空数组。但是如果我把它放在 ngAfterViewChecked() 中,它会正确地显示数组,但这没有任何意义,因为你无法控制何时执行该生命周期钩子或其他任何生命周期钩子。这是改用响应式表单的另一个原因 :P - AT82
显示剩余7条评论
2个回答

3
这段代码对我有效:

    Object.keys((<FormGroup>this.form.form.get('pg1')).controls).forEach(element => {
        console.log(element);
    });

但是关键问题(根据您问题下面的评论)是这段代码所在的位置。我将其添加为提交按钮过程的一部分。

在ngOnInit、ngAfterViewInit和ngAfterViewChecked中,这些元素的值为null/undefined。

如果您只需要知道表单组是否有效,您可以这样做:

    let isValid = this.form.form.get('pg1').valid;

或者,您可以使用选项卡页面,并在任何具有验证错误的页面上显示错误图标,如下所示:

enter image description here

在此示例中,我正在使用模板驱动的表单。表单上的每个输入元素看起来像这样:

        <div class="form-group" 
                [ngClass]="{'has-error': (productNameVar.touched || 
                                          productNameVar.dirty || product.id !== 0) && 
                                          !productNameVar.valid }">
            <label class="col-md-2 control-label" 
                    for="productNameId">Product Name</label>

            <div class="col-md-8">
                <input class="form-control" 
                        id="productNameId" 
                        type="text" 
                        placeholder="Name (required)"
                        required
                        minlength="3"
                        [(ngModel)] = product.productName
                        name="productName"
                        #productNameVar="ngModel" />
                <span class="help-block" *ngIf="(productNameVar.touched ||
                                                 productNameVar.dirty || product.id !== 0) &&
                                                 productNameVar.errors">
                    <span *ngIf="productNameVar.errors.required">
                        Product name is required.
                    </span>
                    <span *ngIf="productNameVar.errors.minlength">
                        Product name must be at least three characters.
                    </span>
                </span>
            </div>
        </div>

您可以在APM-Final文件夹中找到此示例的完整代码,链接如下:https://github.com/DeborahK/Angular-Routing。(这是我在Pluralsight课程“Angular Routing”中使用的代码。)

谢谢,我之前尝试过,但像你所说,在ngOnInit中它是未定义的,所以我认为我只是做错了。我可以添加一个initialized: boolean并调用函数NextPage(){ if(!initialized){ initialized = true; /*Object.keys code goes here */} else {//proceed to next page} }这样在进入下一页之前,您可以获得所有控件。但是,这是不好的设计。我真的不喜欢这变成了一种“问题:遇到障碍;解决方案:重新开始”的情况,但响应式表单可能是答案... - slanden
你到底想做什么?为什么需要获取所有控件的名称?你是打算对它们进行某些操作吗? - DeborahK
你应该能够完全做到这一点,而不需要遍历表单上的所有控件吗?另外,作为另一种方法...我使用选项卡来做类似的事情,而不是防止移动到其他选项卡,我只是在选项卡上添加了一个错误图标。我会在我的答案中添加一个屏幕截图。 - DeborahK
太棒了!是的,我的也是这样工作的。当前选项卡上的任何错误都会在具有问题的控件下显示。 - DeborahK
我会将它添加到我上面的答案中。 - DeborahK
显示剩余4条评论

3
在`myForm.component.html`中,创建一个引用标签以获取对表单的访问权限。
<form #myForm="ngForm" ...>

然后创建一个按钮来调用一个函数以访问表单数据。
<button type="button" class="btn" 
  (click)="getMyFormData(myForm)">
    Get Form Data
</button>

在你的myForm.component.ts文件中。
getMyFormData( f: NgForm ){
  Object.values(f.controls).forEach( ctl => {
    console.log(ctl);  // <-- each control is accessible here
  } );
}

或者只需像这样返回整个表单数据对象:
getMyFormData( f: NgForm ){
  console.log(f.value) // <-- all controls accessible here
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接