【问题标题】:How to use array of objects for controls in Reactive Forms如何在反应式表单中使用对象数组进行控件
【发布时间】:2017-03-23 06:20:57
【问题描述】:

我需要为表单动态创建textarea。我有以下型号:

this.fields = {
      isRequired: true,
      type: {
        options: [
          {
            label: 'Option 1',
            value: '1'
          },
          {
            label: 'Option 2',
            value: '2'
          }
        ]
      }
    };

和形式:

this.userForm = this.fb.group({
      isRequired: [this.fields.isRequired, Validators.required],
      //... here a lot of other controls
      type: this.fb.group({
         options: this.fb.array(this.fields.type.options),
      })
});

部分模板:

<div formGroupName="type">
       <div formArrayName="options">
         <div *ngFor="let option of userForm.controls.type.controls.options.controls; let i=index">
            <textarea [formControlName]="i"></textarea>
         </div>
      </div>
</div>

所以,如您所见,我有一个对象数组,我想使用label 属性在textarea 中显示它。现在我看到[object Object]。如果我将options 更改为一个简单的字符串数组,例如:['Option 1', 'Option 2'],那么一切正常。但我需要使用对象。所以,而不是:

<textarea [formControlName]="i"></textarea>

我试过了:

<textarea [formControlName]="option[i].label"></textarea>

但是,它不起作用。
如何使用对象数组?

这是Plunkr

【问题讨论】:

    标签: forms angular


    【解决方案1】:

    您需要添加一个 FormGroup,其中包含您的 labelvalue。这也意味着表单创建的对象与您的fields 对象具有相同的构建。

    ngOnInit() {
      // build form
      this.userForm = this.fb.group({
        type: this.fb.group({
          options: this.fb.array([]) // create empty form array   
        })
      });
    
      // patch the values from your object
      this.patch();
    }
    

    之后,我们使用您的 OnInit 中调用的方法修补该值:

    patch() {
      const control = <FormArray>this.userForm.get('type.options');
      this.fields.type.options.forEach(x => {
        control.push(this.patchValues(x.label, x.value))
      });
    }
    
    // assign the values
    patchValues(label, value) {
      return this.fb.group({
        label: [label],
        value: [value]
      })    
    }
    

    终于来了一个

    Demo

    【讨论】:

    • 澄清:写this.fb.array([]) 和写new FormArray([]) 一样this.fb.group({})new FormGroup({})
    【解决方案2】:

    AJT_82 的回答对我非常有用,我想我会分享我如何重用他的代码并构建了一个类似的示例 - 一个可能有一个更常见的用例,即邀请几个人同时注册.像这样:

    我认为这可能对其他人有帮助,所以我在这里添加它。

    您可以看到表单是一个简单的电子邮件文本输入数组,每个都加载了一个自定义验证器。您可以在屏幕截图中看到 JSON 结构 - 查看模板中的前行(感谢 AJT),这是一个在开发过程中非常有用的想法,可查看您的模型和控件是否已连接!

    首先,声明我们需要的对象。请注意,3 个空字符串是模型数据(我们将绑定到文本输入):

          public form: FormGroup;
          private control: FormArray;
          private emailsModel = { emails: ['','','']} // the model, ready to hold the emails
          private fb : FormBuilder;
    

    构造函数是干净的(为了更容易测试,只需注入我的 userService 以在提交后发送表单数据):

          constructor(
            private _userService: UserService,
          ) {}
    

    表单是在 init 方法中构建的,包括存储对 emailsArray 控件本身的引用,以便我们稍后检查其子项(实际输入)是否被触摸,如果是,它们是否有错误:

          ngOnInit() {
            this.fb = new FormBuilder;
            this.form = this.fb.group({
              emailsArray: this.fb.array([])
            });
            this.control = <FormArray>this.form.controls['emailsArray'];
            this.patch();    
          }
        
          private patch(): void {
            // iterate the object model and extra values, binding them to the controls
            this.emailsModel.emails.forEach((item) => {
              this.control.push(this.patchValues(item));
            })
          }
    

    这是使用验证器构建每个输入控件(AbstractControl 类型)的内容:

          private patchValues(item): AbstractControl {
            return this.fb.group({
              email: [item, Validators.compose([emailValidator])] 
            })
          }
    

    检查输入是否被触摸以及验证器是否引发错误的 2 个辅助方法(请参阅模板以了解它们的使用方式 - 请注意,我从模板中的 *ngFor 传递数组的索引值) :

          private hasError(i):boolean {
            // const control = <FormArray>this.form.controls['emailsArray'];
            return this.control.controls[i].get('email').hasError('invalidEmail');
          }
          private isTouched(i):boolean {
            // const control = <FormArray>this.form.controls['emailsArray'];
            return this.control.controls[i].get('email').touched;
          }
    

    这是验证器:

        export function emailValidator(control: FormControl): { [key: string]: any } {
            var emailRegexp = /[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,3}$/;
            if (control.value && !emailRegexp.test(control.value)) {
                return { invalidEmail: true };
            }
        }
    

    还有模板:

        <form [formGroup]="form" (ngSubmit)="onSubmit(form.value)" class="text-left">
            <div formArrayName="emailsArray">
                <div *ngFor="let child of form.controls.emailsArray.controls; let i=index">
                    <div class="form-group" formGroupName="{{i}}">
                        <input formControlName="email" 
                               class="form-control checking-field" 
                               placeholder="Email" type="text">
                        <span class="help-block" *ngIf="isTouched(i)">
                            <span class="text-danger" 
                                  *ngIf="hasError(i)">Invalid email address
                            </span>
                        </span>
                    </div>                   
                </div>
            </div>
            <pre>{{form.value | json }}</pre>            
            <div class="form-group text-center">
              <button class="btn btn-main btn-block" type="submit">INVITE</button>
            </div>
        </form>
    

    不管怎样,我一开始就是从这个糟糕的烂摊子开始的——但是如果你看看下面的代码,你可能会更容易理解上面的代码!

          public form: FormGroup;
          public email1: AbstractControl;
          public email2: AbstractControl;
          public email3: AbstractControl;
          public email4: AbstractControl;
          public email5: AbstractControl;
        
          constructor(
            fb: FormBuilder
          ) { 
               this.form = fb.group({
              'email1': ['', Validators.compose([emailValidator])],
              'email2': ['', Validators.compose([emailValidator])],
              'email3': ['', Validators.compose([emailValidator])],
              'email4': ['', Validators.compose([emailValidator])],
              'email5': ['', Validators.compose([emailValidator])],
                });
            this.email1 = this.form.controls['email1'];
            this.email2 = this.form.controls['email2'];
            this.email3 = this.form.controls['email3'];
            this.email4 = this.form.controls['email4'];
            this.email5 = this.form.controls['email5'];
          }
    

    上面在模板中使用了其中的 5 个 div - 不是很干!

        <div class="form-group">
            <input [formControl]="email1" class="form-control checking-field" placeholder="Email" type="text"> 
            <span class="help-block" *ngIf="form.get('email1').touched">
                <span class="text-danger" *ngIf="form.get('email1').hasError('invalidEmail')">Invalid email address</span>
            </span> 
        </div>
    

    【讨论】:

      【解决方案3】:

      我猜FormControlName 不可能。

      您可以使用ngModel .. 看看您修改后的 plunker:

      http://plnkr.co/edit/0DXSIUY22D6Qlvv0HF0D?p=preview

      @Component({
        selector: 'my-app',
        template: `
          <hr>
          <form [formGroup]="userForm" (ngSubmit)="submit(userForm.value)">
           <input type="checkbox" formControlName="isRequired"> Required Field
          <div formGroupName="type">
             <div formArrayName="options">
               <div *ngFor="let option of userForm.controls.type.controls.options.controls; let i=index">
                  <label>{{ option.value.label }}</label><br />
      
                  <!-- change your textarea -->
                  <textarea [name]="i" [(ngModel)]="option.value.value" [ngModelOptions]="{standalone: true}" ></textarea>
               </div>
            </div>
          </div>
      
          <button type="submit">Submit</button>
          </form>
          <br>
          <pre>{{userForm.value | json }}</pre>
        `,
      })
      export class App {
        name:string;
        userForm: FormGroup;
        fields:any;
      
        ngOnInit() {
          this.fields = {
            isRequired: true,
            type: {
              options: [
                {
                  label: 'Option 1',
                  value: '1'
                },
                {
                  label: 'Option 2',
                  value: '2'
                }
              ]
            }
          };
      
          this.userForm = this.fb.group({
            isRequired: [this.fields.isRequired, Validators.required],
            //... here a lot of other controls
            type: this.fb.group({
               // .. added map-function
               options: this.fb.array(this.fields.type.options.map(o => new FormControl(o))),
            })
          });
        }
      
        submit(value) {
          console.log(value);
        }
      
        constructor(private fb: FormBuilder) {  }
      
        addNumber() {
          const control = <FormArray>this.userForm.controls['numbers'];
          control.push(new FormControl())
        }
      }
      

      【讨论】:

        【解决方案4】:

        你可以试试这个

        打字稿:

         ngOnInit(): void {
        this.user = this.fb.group({
          Title: ['1'],
          FirstName: ['', Validators.required],
          LastName: ['', Validators.required],
          ContactNumbers: this.fb.array([
            this.fb.group({
              PhoneNumber: ['', [Validators.required]],
              IsPrimary: [true],
              ContactTypeId: [1]
            })
          ]),
          Emails: this.fb.array([
            this.fb.group({
              Email: ['', [Validators.required, Validators.email]],
              IsPrimary: [true]
            })
          ]),
          Address: this.fb.group({
            Address1: ['', Validators.required],
            Address2: [''],
            Town: [''],
            State: ['UP'],
            Country: [{ value: 'India', disabled: true }],
            Zip: ['', Validators.required]
          })
        });
         }
        
        
        
         get Emails() {
            return this.landlord.get('Emails') as FormArray;
          }
        

        添加和删除

         addMoreEmail(index: number) {
        if (index == 0) {
          this.Emails.push(this.fb.group({ Email: ['', [Validators.required, Validators.email]], IsPrimary: [false] }));
        } else {
          this.Emails.removeAt(this.Emails.value.indexOf(index));
        }
        

        }

        HTML

        <form [formGroup]="user"
          <div formArrayName="Emails">
                                        <div *ngFor="let email of Emails.controls; let i=index">
                                            <div class="row" [formGroup]="email">
                                                <div class="col-sm-10">
                                                    <div class="form-group">
                                                        <label for="i" class="label">Email</label>
                                                        <input type="email" nbInput fullWidth id="i" placeholder="Email"
                                                            formControlName="Email" required>
                                                    </div>
                                                </div>
                                                <div class="col-sm-2 position-relative">
                                                    <nb-icon icon="{{i==0?'plus':'minus'}}-round" pack="ion"
                                                        (click)="addMoreEmail(i)">
                                                    </nb-icon>
                                                </div>
                                            </div>
                                        </div>
        
                                    </div></div>
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2023-01-13
          • 2019-09-30
          • 2022-01-06
          • 2018-07-29
          • 1970-01-01
          相关资源
          最近更新 更多