【问题标题】:Angular - Rendering a collection of child controls without a FormArray?Angular - 渲染没有FormArray的子​​控件集合?
【发布时间】:2018-07-15 20:58:48
【问题描述】:

TL、DR;

当子控件集合不是FormArray时,怎么渲染?

// 'foo', 'bar' and 'baz' are actually names chosen by the user.
// and are *not* known ahead-of-time.

let form = this.fb.group({
    fields: this.fb.group({
      foo: this.fb.control(''),
      bar: this.fb.control(''),
      baz: this.fb.control('')
    })
});

我将FormGroup 用于fields,因为value 属性产生的JSON 表示必须是object 而不是array

如果没有*ngForfields 怎么能被渲染——因为fields 是不可迭代的,它不会工作。


细节;

我想呈现属于Angular Form 的一组子控件,但不是FormArray 中。

// Use FromBuilder to create the form.
constructor(private readonly fb: FormBuilder) { }

表单必须表示一个由 fields 组成的结构,其中包含键/值对:

// Create the form with no/empty fields to start.
let form = this.fb.group({
    fields: this.fb.group({})
});

我不知道fields 中将存在多少子控件 - 它们将通过用户操作添加。

此时,fields 通常是 FormArray,但我必须使用 FormGroup,因为来自 value 表单的表示必须是 JSON object 而不是 JSON @ 987654344@.

在应用加载后的某个时间点,子控件使用addControl 添加到fields。控件名称实际上由用户选择

为简单起见,我在这里将它们硬编码为foobarbaz

const fields = form.get('fields');

const foo = new FormControl('');
fields.addControl('foo', foo);

const bar = new FormControl('');
fields.addControl('bar', bar);

...

const baz = new FormControl('');
fields.addControl('baz', baz);

理想情况下我会使用*ngFor,但由于controls 不可迭代,这将不起作用:

<ng-container [formGroup]="form">

  <ng-container formGroupName="fields">

       <mat-form-field *ngFor="let f of form.get('fields').controls">

         <input matInput ...>

       </mat-form-field>

  </ng-container>

</ng-container>

另外,我不知道fields 中的任何子控件的名称 - 因为如前所述,新添加的控件的名称实际上是由用户选择的。

【问题讨论】:

    标签: javascript angular angular-material


    【解决方案1】:

    当您想将ngFor 与对象的键一起使用时,您可以简单地使用Object.keys 从对象中提取这些键,然后遍历该数组。

    由于它将是非常通用的东西(您可能希望重用它),并且(理想情况下)将通过模板处理它,因此为此创建一个管道是个好主意:

    @Pipe({
      name: 'keys'
    })
    export class KeysPipe implements PipeTransform {
      transform(value: any): any[] {
        return Object.keys(value)
      }
    }
    

    现在你可以做类似的事情 &lt;div *ngFor="let a of resultForm.controls | keys"&gt;

    另一个有趣的部分是防止将已经存在的键添加到表单中。这是一个带有自定义验证器的完整示例:

    TS:

    export function forbiddenFieldNameValidator(form: FormGroup): ValidatorFn {
      return (control: AbstractControl): {[key: string]: any} | null => {
        const fieldToAdd: string = control.value;
        const forbidden = !!form.controls[fieldToAdd];
    
        return forbidden ?
          {'forbiddenFieldName': { value: control.value }} :
          null;
      };
    }
    
    @Component({
      selector: 'my-app',
      templateUrl: './app.component.html',
      styleUrls: [ './app.component.css' ]
    })
    export class AppComponent implements OnInit {
      resultForm: FormGroup = new FormGroup({});
    
      addFieldGroup: FormGroup = this.fb.group({
        addField: ['', [Validators.required, forbiddenFieldNameValidator(this.resultForm)]]
      });
    
      constructor(private fb: FormBuilder) {}
    
      ngOnInit(): void { }
    
      addField(fieldName: string) {
        this.resultForm.addControl(
          fieldName,
          new FormControl('')
        );
    
        this.addFieldGroup.reset();
      }
    }
    

    HTML:

    <form [formGroup]="addFieldGroup" (ngSubmit)="addField(addFieldGroup.controls['addField'].value)">
      <input type="text" formControlName="addField">
    
      <button type="submit" [disabled]="addFieldGroup.invalid">Add field to form</button>
    
      <div *ngIf="addFieldGroup.dirty && addFieldGroup.controls['addField'].errors as errors" class="error">
        <div *ngIf="errors['required']">This field is required</div>
        <div *ngIf="errors['forbiddenFieldName']">The key "{{ addFieldGroup.controls['addField'].value }}" is already into the dynamic form</div>
      </div>
    </form>
    
    Dynamic form
    
    <form [formGroup]="resultForm">
      <div *ngFor="let controlName of resultForm.controls | keys">
        {{ controlName }} <input type="text" [formControlName]="controlName" [placeholder]="controlName">
      </div>
    </form>
    
    Form value
    <pre>{{ resultForm.value | json }}</pre>
    

    这是一个有效的堆栈闪电战,您可以尝试将动态值添加到最终表单中,并查看添加这些值的输入是否已存在:

    https://stackblitz.com/edit/angular-w2ibtb

    旁注: 对于管道,请注意我们必须将其设置为不纯,因为我们没有直接处理表单的 controls 属性,并且在引擎盖下,每次添加财产入其中。因此,传递给管道的引用是相同的,但我们仍然想要更新视图。这就是为什么我们必须将pure 设置为false。

    【讨论】:

      【解决方案2】:

      另一种选择是制作一个控件数组

      controls:FormControl[]=[] //declare a variable
      form:FormGroup //Your form
      
      constructor(private fb: FormBuilder) { }
      //When you defined the form, you create the array
      this.form=this.fb.group({
          fields: this.fb.group({
            foo: this.fb.control(''),
            bar: this.fb.control(''),
            baz: this.fb.control('')
          })
          //usign map from Object.keys
          Object.keys((this.form.get('fields') as FormGroup).controls).map(control=>{
            this.controls.push((this.form.get('fields') as FormGroup).get(control) as FormControl);
          });
      
      //Your .html is like
      <div *ngFor="let control of controls">
          <input [formControl]="control">
        </div>
        {{form.value |json}}
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-06-11
        • 2011-02-23
        • 2020-06-25
        • 2019-06-20
        • 2011-12-19
        • 2018-05-19
        相关资源
        最近更新 更多