如何在多个组件之间使用Angular2的FormBuilder

How to use Angular 2's FormBuilder between multiple components

我正在尝试在 Ionic 2 中的页面中使用 FormBuilder。

首先,这是我的环境详细信息: 运行 Windows 10,运行 ionic --version 给我 2.0.0-beta.35

这是我的 package.json 文件的一部分:

...
"@angular/common": "2.0.0-rc.3",
"@angular/compiler": "2.0.0-rc.3",
"@angular/core": "2.0.0-rc.3",
"@angular/forms": "^0.3.0",
"@angular/http": "2.0.0-rc.3",
"@angular/platform-browser": "2.0.0-rc.3",
"@angular/platform-browser-dynamic": "2.0.0-rc.3",
"ionic-angular": "2.0.0-beta.10",
"ionic-native": "1.3.2",
"ionicons": "3.0.0"
...

其次,主要涉及两个文件:

insight.ts

import { Component } from '@angular/core';
import {NavController, NavParams} from 'ionic-angular';
import {
  REACTIVE_FORM_DIRECTIVES,
  FormBuilder,
  FormControl,
  FormGroup
} from '@angular/forms';
import { App, Insight } from '../../models';
import { InsightProvider } from '../../providers/insight/insight.service';
import { InsightImage, InsightLabel, InsightLink, InsightQuestion, InsightThought, InsightTodo, InsightVideo } from './shared';

@Component({
  templateUrl: 'build/pages/insight/insight.html',
  directives: [REACTIVE_FORM_DIRECTIVES, InsightImage, InsightLabel, InsightLink, InsightQuestion, InsightThought, InsightTodo, InsightVideo],
  providers: [App, InsightProvider, FormBuilder]
})
export class InsightPage {

  canAdd: boolean;
  showDetails: boolean;
  newInsight: Insight;
  insightForm: FormGroup;

  constructor(private insightProvider: InsightProvider,
              private params: NavParams) {
    this.insightForm = new FormGroup({
      type: new FormControl('', []),
      todo: new FormControl('', []),
      checked: new FormControl(false, []),
      imageUrl: new FormControl('', []),
      link: new FormControl('', []),
      url: new FormControl('', []),
      label: new FormControl('', []),
      question: new FormControl('', []),
      answer: new FormControl('', []),
      title: new FormControl('', []),
      details: new FormControl('', []),
    });
  }

  ngOnInit() {
    this.canAdd = false;
    this.showDetails = true;
  }

  addNewInsight() {
    if (this.newInsight.type) {
      this.insightProvider.createInsight(this.newInsight)
      .subscribe(response => {
        this.newInsight.setId(response.data.id);
        this.newInsight.title = '';
        console.log(response);
      });
    }
  }

  deleteClicked(index: number) {
    console.log('Clicked on ' + index);
    this.insightProvider.deleteInsight(this.newInsight)
    .subscribe(data => {
      console.log(data);
    });
  }


}

insight.html

<form [ngFormModel]="insightForm" (ngSubmit)="createNewInsight()">
      <ion-item>
        <ion-label for="type">Insight Type</ion-label>
        <ion-select name="type" id="type" [formControl]="type">
          <ion-option value="label">Label</ion-option>
          <ion-option value="thought">Thought</ion-option>
          <ion-option value="link">Link</ion-option>
          <ion-option value="question">Question</ion-option>
          <ion-option value="todo">Todo</ion-option>
          <ion-option value="image">Image</ion-option>
          <ion-option value="video">Video</ion-option>
        </ion-select>
      </ion-item>

      <div [ngSwitch]="type">
          <insight-image [form]="insightForm" *ngSwitchCase="'image'"></insight-image>
          <insight-label [form]="insightForm" *ngSwitchCase="'label'"></insight-label>
          <insight-link [form]="insightForm" *ngSwitchCase="'link'"></insight-link>
          <insight-question [form]="insightForm" *ngSwitchCase="'question'"></insight-question>
          <insight-thought [form]="insightForm" *ngSwitchCase="'thought'"></insight-thought>
          <insight-todo [form]="insightForm" *ngSwitchCase="'todo'"></insight-todo>
          <insight-video [form]="insightForm" *ngSwitchCase="'video'"></insight-video>
      </div>

      <button type="submit" block primary text-center (click)="addNewInsight()" [disabled]="!newInsight.type">
        <ion-icon name="add"></ion-icon> Add Insight
      </button>
    </form>

如您所见,我正在尝试将 FormGroup 对象传递到多个组件中,以便我可以使用它们。

这是其中一个组件的示例(现在是最小版本):

<ion-item>
  <ion-label floating for="link">Link</ion-label>
  <ion-input type="text" name="link" id="link" [formControl]="link"></ion-input>
</ion-item>

<ion-item>
  <ion-label floating for="url">URL</ion-label>
  <ion-input type="text" id="url" name="url" [formControl]="url"></ion-input>
</ion-item>

我现在面临的问题是这个错误:

我认为正在发生的事情是 FormBuilder 正在寻找我在打字稿文件中声明的给定名称(例如 todo、imageUrl、link 等),但因为它在我的其他组件中,它出错了,认为它不存在。

出现此错误的原因可能是什么?我上网查了也没找到相关问题。

仅供参考,我在组件中而不是在同一页面中需要它们的原因是因为将来每个输入的功能都会不同,因此需要给每个组件一个 "Single Responsibility"。

提前致谢

对于其他有问题的人

Cannot find control with unspecified name attribute.

问题是您忘记在表单输入元素上定义 formControlName

formControlName="url"

如果您在修复输入后遇到 No provider for NgControl,则说明 Ionic2 在 Fom 处理方面的破坏性更改存在问题。您有机会通过导入新的表单组件来修复您的代码:

import { disableDeprecatedForms, provideForms } from '@angular/forms';

但你可能还会面临越来越多的问题。要真正修复您的代码:

  • 更新到最新的测试版
  • 将您的表单简化为 2 个简单的输入
  • 重写您的表单并使其正常工作
  • 添加其余元素

关于 FormBuilder 和验证的好教程 https://blog.khophi.co/ionic-2-forms-formbuilder-and-validation/

作为 Angular 2+ 中的一般说明,我发现采用一致的方法来指定 formGroupName、formArrayName 和 formGroupName 节省了大量时间,而且我还保持理智。我之前浪费了很多时间。

要么使用

formXXXName="literal value"

formXXXName="{{value expression}}"

[formXXXName]="'literal value'" (single tick within double tick)

[formXXXName]="value expression"

我一直在混合它们,这让我很伤心。

最近项目的示例:

案例一:正确使用括号表示法。

    <label>Notes</label>
    <div class="panel panel-default">
        <div class="panel-body">
            <div [formArrayName]="'notes'">
                <div *ngFor="let note of outletForm.controls.notes.controls; let i=index">
                    <input type="text" [formControlName]="i" class="form-control" />
                </div>
            </div>
        </div>
    </div>

案例2:正确使用非括号表示法。

    <label>Notes</label>
    <div class="panel panel-default">
        <div class="panel-body">
            <div formArrayName="notes">
                <div *ngFor="let note of outletForm.controls.notes.controls; let i=index">
                    <input type="text" formControlName="{{i}}" class="form-control" />
                </div>
            </div>
        </div>
    </div>

请注意上面使用“{{”和“}}”来强制 Angular 处理通常为文字字符串值的内容(因为使用了无括号语法) 作为 EL 表达式。

案例 3:错误使用括号表示法

    <label>Notes</label>
    <div class="panel panel-default">
        <div class="panel-body">
            <div [formArrayName]="notes">
                <div *ngFor="let note of outletForm.controls.notes.controls; let i=index">
                    <input type="text" formControlName="{{i}}" class="form-control" />
                </div>
            </div>
        </div>
    </div>

在这种情况下,formArrayName 的值 "notes" 被视为 EL 表达式。由于我的组件没有这样的成员,因此它的计算结果为空。因此,持续出现以下错误:

OutletFormComponent.html:153 ERROR Error: Cannot find control with unspecified name attribute
    at _throwError (forms.es5.js:1830)
    at setUpFormContainer (forms.es5.js:1803)
    at FormGroupDirective.addFormArray (forms.es5.js:4751)
    at FormArrayName.ngOnInit (forms.es5.js:5036)
    at checkAndUpdateDirectiveInline (core.es5.js:10793)
    at checkAndUpdateNodeInline (core.es5.js:12216)
    at checkAndUpdateNode (core.es5.js:12155)
    at debugCheckAndUpdateNode (core.es5.js:12858)
    at debugCheckDirectivesFn (core.es5.js:12799)
    at Object.eval [as updateDirectives] (OutletFormComponent.html:159)

幸运的是,堆栈最底部给出的行号指向有问题的行。

案例 4:不正确使用非括号表示法

    <label>Notes</label>
    <div class="panel panel-default">
        <div class="panel-body">
            <div [formArrayName]="'notes'">
                <div *ngFor="let note of outletForm.controls.notes.controls; let i=index">
                    <input type="text" formControlName="i" class="form-control" />
                </div>
            </div>
        </div>
    </div>

在这种情况下,formControlName 的值 "i" 将被解释为文字字符串,但我们实际上打算引用包含 *ngFor 指令的索引变量 'i'。这个错误的结果是:

OutletFormComponent.html:161 ERROR Error: Cannot find control with path: 'notes -> i'
    at _throwError (forms.es5.js:1830)
    at setUpControl (forms.es5.js:1738)
    at FormGroupDirective.addControl (forms.es5.js:4711)
    at FormControlName._setUpControl (forms.es5.js:5299)
    at FormControlName.ngOnChanges (forms.es5.js:5217)
    at checkAndUpdateDirectiveInline (core.es5.js:10790)
    at checkAndUpdateNodeInline (core.es5.js:12216)
    at checkAndUpdateNode (core.es5.js:12155)
    at debugCheckAndUpdateNode (core.es5.js:12858)
    at debugCheckDirectivesFn (core.es5.js:12799)

感谢所有 Angular2+ 开发人员和专家。

使用 FormBuilder 时,您必须在模板中定义控件,以便 FormBuilder 可以识别控件。

所以在你的模板中有一个名为 'insightForm' 的 formGroup 和一个控件 'url':

<form [formGroup]="insightForm" (ngSubmit)="onSubmit(insightForm.value)">
    <ion-input [formControl]="insightForm.controls['url']"></ion-input>
</form>