angular2(2.0.0)转发器使用反应形式和模板

时间:2016-09-19 14:05:49

标签: angular angular2-forms

我正在尝试做什么:我正在构建一个可重复使用的FormGroups(反应形式)转发器,其中转发器的通用部分提供了添加/删除控件和逻辑以及所有想要的组件使用它只需要提供必须重复的模板。我已经检查了

显示了如何通过模板驱动的方式来完成这样的事情。

我被困的地方:我附加到formControlName中的控件的<template>指令未被转发器的通用部分选中:

Error: Cannot find control with name: 'id'
at _throwError (https://unpkg.com/@angular/forms/bundles/forms.umd.js:1527:15)
at setUpControl (https://unpkg.com/@angular/forms/bundles/forms.umd.js:1465:13)
at FormGroupDirective.addControl (https://unpkg.com/@angular/forms/bundles/forms.umd.js:3889:13)
at FormControlName._setUpControl (https://unpkg.com/@angular/forms/bundles/forms.umd.js:4318:48)
at FormControlName.ngOnChanges (https://unpkg.com/@angular/forms/bundles/forms.umd.js:4264:22)

代码:plunkr

RepeaterComponent2

@Component({
    selector: 'repeater2',
    template: `
        <div *ngIf="formArray">debug formArray: {{formArray.length}}</div>
        <div [formGroup]="formGroup">
            <input type="button" value="add" (click)="addRow()" noBootstrap>
            <div formArrayName="values" class="repeater" *ngFor="let row of model; let i = index">
                <div [formGroupName]="i" class="repeaterRow">
                    <input type="button" value="remove" (click)="removeRow(row, i)"  noBootstrap>
                    <template [ngTemplateOutlet]="itemTemplate" [ngOutletContext]="{item: row}"></template>
                </div>
            </div>
        </div>
    `
})
export class RepeaterComponent2 implements OnInit {
    /* initial model */
    @Input() model: any[] = [];
    /* function to create a new item */
    @Input() newItem: (i) => {};
    /* function to create a new FormGroup to fit the item */
    @Input() newItemFormGroup: (fb: FormBuilder, model: any) => FormGroup;
    /* root form group where the local FormArray is attached to */
    @Input() formGroup: FormGroup;    

    @Output() repeaterArrayChanged: EventEmitter<any[]>;
    @ContentChild(TemplateRef) itemTemplate: TemplateRef<any>;    

    formArray: FormArray;    

    constructor(private fb: FormBuilder) {
        this.repeaterArrayChanged = new EventEmitter<any[]>();
    }    

    ngOnInit() {
        this.formArray = this.fb.array([]);
        this.formGroup.addControl('values', this.formArray);
        this.model.forEach((item: any) => this.addItemFormGroup(item))
    }    

    addRow() {
        let id = this.model.length + 1;
        let obj = this.newItem(id);
        this.model.push(obj);
        this.addItemFormGroup(obj);
        this.repeaterArrayChanged.emit(this.model);
    }    

    addItemFormGroup(item: any) {
        this.formArray.push(this.newItemFormGroup(this.fb, item));
    }    

    removeItemFormGroup(i) {
        this.formArray.removeAt(i);
    }    

    removeRow(row, i) {
        this.model = this.model.filter((x: any) => x !==row);
        this.removeItemFormGroup(i);
        this.repeaterArrayChanged.emit(this.model);
    }
}

应该如何使用:

    @Component({
    selector: 'my-app',
    template: `
        <div>
            <div>{{debugForm | json}}</div>
            <form [formGroup]="formGroup">
                <!-- passing in:
                    - the root form group (the formArray of the repeater adds itself to it)
                    - the initial model items
                    - a callback that is used to create a new model item when "add" is clicked 
                    - a callback that is used to create a "suitable" FormGroup for the passed model item 
                -->
                <repeater2 [formGroup]="formGroup" [model]="model.items" [newItem]="newItem" [newItemFormGroup]="newItemFormGroup">
                    <template let-row="item">
                        <span>{{row | json}}</span>
                        <!-- !!! THIS FORM CONTROL IS NOT PICKED UP !!! -->
                        <input type="text" formControlName="id" noBootstrap>
                    </template>
                </repeater2>
            </form>
        </div>
    `
})
export class RepeaterTestComponent2 implements OnInit {    

    model: Model;
    formGroup: FormGroup;    

    constructor(private fb: FormBuilder) {
        this.model = new Model(1, [
            new Item(1, 'one'),
            new Item(2, 'two'),
            new Item(3, 'three')
        ]);
    }    

    ngOnInit() {
        this.formGroup = this.fb.group({});
    }    

    newItem(i): any {
        return new Item(i, 'xxx');
    }    

    newItemFormGroup(fb: FormBuilder, model: any): any {
        return fb.group({
            id: []
        });
    }    

    get debugForm() {
        return {
            value: this.formGroup.value
        };
    }
}

1 个答案:

答案 0 :(得分:0)

是的,这可以按照下面的类似问题进行。

基本上,您的元素必须实现ControlValueAccessorngModel注入的formControlName接口,如下所示:

@Directive({
  selector: 'control',
  providers: [
    {provide: NG_VALUE_ACCESSOR, multi: true, useExisting: SwitchControlComponent}
  ]
})
export class SwitchControlComponent implements ControlValueAccessor {
  isOn: boolean;
  _onChange: (value: any) => void;

  writeValue(value: any) {
    this.isOn = !!value;
  }

  registerOnChange(fn: (value: any) => void) {
    this._onChange = fn;
  }

  registerOnTouched() {}

  toggle(isOn: boolean) {
    this.isOn = isOn;
    this._onChange(isOn);
  }
}

请注意,您必须(AFAIK)然后将表单传递给已转换的模板 - 请参阅let-form="form"

<repeater2 [formGroup]="formGroup" [model]="model.items" [newItem]="newItem" [newItemFormGroup]="newItemFormGroup">
    <template let-row="item" let-form="form">
        <span>{{row | json}}</span>
        <input control type="text" formControlName="id" noBootstrap>
    </template>
</repeater2>

进一步阅读:

Angular2: Bind form context to ngTemplateOutlet

With new forms api, can't add inputs from child components without adding additional form tags