web-dev-qa-db-ja.com

Angular 2. Reactive Formsのコントロールにオブジェクトの配列を使用する方法

フォームの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 です

13
user348173

labelvalueを含むFormGroupを追加する必要があります。これは、フォームによって作成されたオブジェクトが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]
  })    
}

最後に、ここに

デモ

22
AJT82

AJT_82からの回答は私にとってとても役に立ちました。彼のコードを再利用し、同様の例を構築する方法を共有すると思いました-一度にサインアップするために複数の人を招待するより一般的なユースケースがあるかもしれませんこのような: - form screenshot

これは他の人にも役立つかもしれないと思ったので、ここに追加します。

フォームは、電子メール用のテキスト入力の単純な配列であり、それぞれにカスタムバリデータがロードされていることがわかります。スクリーンショットでJSON構造を確認できます-テンプレートのpre行(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,
  ) {}

フォームはemailsArrayコントロール自体への参照を保存するなど、initメソッドに組み込まれているため、後でその子(実際の入力)がタッチされているかどうかを確認できます。

  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));
    })
  }

これは、バリデーターで(AbstracControl型の)各入力コントロールを構築するものです。

  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'];
  }

上記はテンプレートでこれらのdivのうち5つを使用しました-非常に乾燥していません!

<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>
11
rmcsharry

FormControlNameでは不可能だと思います。

ngModelを使用できます。変更したプランカーを見てください。

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())
  }
}
0
mxii