角度动态表单:以编程方式更新值是不好的做法吗?

时间:2017-09-01 11:33:47

标签: angular typescript angular-forms

我已经实现了一个通用的动态表单组件:

@Component({
    selector: 'dynamic-form',
    templateUrl: './dynamic-form.component.html'
})
export class DynamicFormComponent implements OnInit, OnChanges {

    @Input() inputs: InputBase<any>[] = [];
    @Input() submitLabel: string;
    @Input() globalValidator: ValidatorFn | undefined;      

    @Output() onSubmit: EventEmitter<any> = new EventEmitter<any>();
    @Output() onChanges: EventEmitter<any> = new EventEmitter<any>();
    @Output() onValid: EventEmitter<boolean> = new EventEmitter<boolean>();

    private wasValid: boolean = false;
    form: FormGroup;

    constructor(private fb: FormBuilder) { }

    ngOnInit() {
        //Let's get the needed controls to create a FormGroup
        this.form = this.generateFormGroup();
    }

    ngOnChanges() {
        this.form = this.generateFormGroup();
    }

    private generateFormGroup(): FormGroup {
        let group: any = {};
        this.inputs && this.inputs.forEach(ib => {
                ib.addtoFormGroup(group);
            });

        let form: FormGroup;
        if (this.globalValidator) {
            form = this.fb.group(group, { validator: this.globalValidator });
        } else {
            form = this.fb.group(group);
        }

        form.valueChanges.subscribe(data => {
            if (this.form.valid !== this.wasValid) {
                this.wasValid = this.form.valid;
                this.onValid.emit(this.wasValid);
            }
            this.onChanges.emit(this.form.value);
        });
        return form;
    }
  submit() {
        this.onSubmit.emit(this.form.value);
    }
    isValid(): boolean {
        return this.form.valid;
    }
    getValue() {
        return this.form.value;
    }
}

模板如下:

    <form *ngIf="form" role="form" class="form-horizontal" (ngSubmit)="submit()" [formGroup]="form">
        <div class=" row ibox-content">
            <div class="col-md-12">
                <div *ngFor="let input of inputs" class="form-group">
                    <df-input [input]="input" [form]="form" [options]="input.options"></df-input>
                </div>
            </div>
            <div class="row col-md-12">
                <button class="btn btn-primary pull-right" type="submit" [disabled]="!form.valid">{{submitLabel| translate}}</button>
            </div>
        </div>
    </form>

正如您所看到的,此组件接收几个类的列表,这些类扩展InputBase并使用它们生成df-input组件:此类的唯一有趣的部分是我的问题如下方法,它有助于填充将用于创建FormGroup

的对象
    addtoFormGroup(groupConf: {[key:string]:any}) {
        if (groupConf[this.key]) {
            throw new Error('The form already has an input with the same name: '+ this.key);
        }
        groupConf[this.key]=[this.value,this.validators];   
    }

现在df-input组件如下(简化):

    @Component({
        selector: 'df-input',
        templateUrl: './input.component.html',
        styleUrls: ['./input.component.css']
    })
    export class InputComponent implements OnInit, OnChanges,DoCheck {

        @Input() input: InputBase<any>;
        @Input() form: FormGroup;

        private wasValid: boolean = false;
        differ: any;

        constructor(private differs: KeyValueDiffers) {

        }

        ngOnInit() {
            this.differ = this.differs.find(this.input).create();
            if (this.input.controlType === 'dropdown') {
                let configuration: InputDropdownConfiguration<any>= (this.input as InputDropdownConfiguration<any>);
                if (configuration.options && configuration.options.length===1) {
                    if (this.form) {
                        (this.form.controls[this.input.key] as AbstractControl).setValue(configuration.options[0].value)
                    }
                }
            }
        }

        ngOnChanges() {
            this.ngOnInit();
        }

        ngDoCheck() {
            var changes = this.differ.diff(this.input);
        }

        get isValid(): boolean {
            if (this.form && this.form.controls && this.form.controls[this.input.key]) {
                return this.form.controls[this.input.key].valid || !this.form.controls[this.input.key].touched;
            }
            return true;
        }

    }

及其模板

    <div [formGroup]="form" class="form-group" [class.has-error]="!isValid">

        <label *ngIf="input.label" [attr.for]="input.key" class="control-label col-sm-{{labelWidth}}">
            <ng-container *ngIf="input.isRequired">* </ng-container>{{input.label | translate}}
        </label>

        <div [ngSwitch]="input.controlType" class="col-sm-{{inputWidth}}">

            <input *ngSwitchCase="'text'" class="form-control" [formControlName]="input.key" [type]="input.type"
            [name]="input.key" [readonly]="input.readonly" placeholder="{{input.placeholder |translate }}">

            <select *ngSwitchCase="'dropdown'" [formControlName]="input.key" class="form-control">
                <option *ngFor="let opt of options" [selected]="input.value == opt.value" value="{{opt.value}}">{{opt.text | translate}}</option>
            </select>

        </div>
        <div class="help-block col-md-10 col-md-offset-1" *ngIf="!isValid" [hidden]="!isValid">{{input.errorMsg | translate}}</div>
    </div>

这是有效的,但我不确定如何扩展它以满足我的下一个要求:我需要一个<select>元素,它始终是最终的&#34;其他&#34;选项。 如果用户选择此选项,则应显示输入文本,并且可以输入更多信息。

这意味着两个元素,但我想表现为只有一个:如果&#34;其他&#34;选择了选项,然后表单模型应该忽略 选择并获取输入文本值,但我不想将这两个元素添加到formGroup。 我正在考虑不将属性[formControlName]="input.key"添加到任何元素,而是以编程方式,当所选选项更改时, 检查它是否是&#34;其他&#34;然后激活输入字段。无论如何,我将使用

将新值添加到表单中
form.controls[input.key].setValue(<new_value>)

这是一种不好的做法吗?我在更新组件状态时遇到了很多问题,我正在努力保持智能哑组件的方法,但对我来说这不是一件容易的事。

1 个答案:

答案 0 :(得分:0)

您可能想要创建Validation服务或类似的服务,并将所有自定义验证器放在那里。

例如,这就是我所拥有的:

import { Injectable } from '@angular/core';
import { FormGroup, AbstractControl } from '@angular/forms';

import { shouldMatch } from './should-match';

@Injectable()
export class Validation {
    shouldMatch(...props: string[]) {
        return (group: FormGroup): any => shouldMatch(group, ...props);
    }
}

应-match.ts:

import { FormGroup } from '@angular/forms';

export function shouldMatch(group: FormGroup, ...props: string[]): any {
    const ctrls = group.controls;
    const len = props.length;

    for (let i = 1; i < len; i++) {
        if (ctrls[props[0]]) {
            if (ctrls[props[0]].value !== ctrls[props[i]].value) {
                return {invalid: true};
            }
        } else {
            throw new Error(`The property '${props[0]}' passed to 'shouldMatch()' was not found on the form group.`);
        }
    }

    return null;
}

我在一个我想要两个密码匹配的地方使用它(它将属性作为字符串,表示表单组中的表单控件名称应该都具有相同的值):

this.form = this.fb.group({
    paswords: this.fb.group({
        password: [null, Validators.required],
        passwordConfirmation: [null, Validators.required]
    }, {validator: this.validation.shouldMatch('password', 'passwordConfirmation')}
})