Angular 2自定义复合控件

时间:2017-03-01 07:35:55

标签: javascript angular

我正在尝试为Angular 2创建自定义复合控件。我的要求是我需要创建一个通用文件选择器控件,允许用户使用html5输入选择 文件[通过输入文件的URL来输入

我决定创建通用表单控件,为两个子控件实现ControlValueAccessor接口,因为它们将在其他地方单独使用。

我正在尝试将它们包装在文件 - 选择器 - 本地 - 或 - 远程(缺少更好的单词)控件中。该外部控件应负责发出所选文件的内容,而不关心文件的选择方式。

将值一直传播到消费表单很容易。每次子控件将值传播到中间控件时,中间控件都会使用registerChange回调将其进一步向链中继到消费者。

但是,我无法传播子控件中可能出现的验证错误。我需要将错误一直传播到消费表单,以便它们可以被本地化。

E.g。如果用户在remote-file-picker子控件中输入了无效的url,则该子控件的验证函数将触发正确的错误。该错误被赋予中间控制。如何将此invalid-url错误一直传播到使用表单?

更广泛地说,是否有关于如何在Angular 2中创建复合控件的具体指导?我找不到包装其他自定义控件的自定义控件的任何示例,所以我不确定我是否正确执行。

换句话说,给出:

形式:

outerForm = new FormGroup({
    file: new FormControl(null, Validators.required)
});

<form [formGroup]="outerForm">
    <File-Picker-Local-Or-Remote formControlName="file"></File-Picker-Local-Or-Remote>
    <span class="error">******???******</span>
</form>

文件选取器-LOCAL-OR-远程

innerForm = new FormGroup({
    local: new FormControl(),
    remote: new FormControl(null, Validators.pattern('http://...'))
});

<input type="file" formControlName="local" />
<input type="text" formControlName="remote" />

当远程子控件验证失败时,它会将错误代码提供给innerForm。如何将这些错误消息传播到外部表单,以便我可以用适当的验证消息替换****** ??? ******?

编辑:我应该注意到,有很多方法可以解决问题或绕过这个问题,包括使用事件发射器构建我自己的解决方案,首先没有复合控制等等。

我真正感兴趣的是创建可重用和可扩展表单控件的 Angular 2方式,消费者可以像任何其他表单控件一样进行交互,并且可以由其他开发人员进一步构建创造更多高级别的控制。

1 个答案:

答案 0 :(得分:4)

一位同事和我不久前想到了这一点,但这里有解决其他任何绊脚石的人。

关键是在复合组件中实现ControlValueAccessor和Validator接口。

E.g。

自定义日期控件,实现ControlValueAccessor

@Component({
  ...
  providers: [{
    provide: NG_VALUE_ACCESSOR,
    useExisting: CustomDateControl),
    multi: true
  }]
})
export class CustomDateControl implements ControlValueAccessor {
  // implement ControlValueAccessor
}

自定义时间控件,实现ControlValueAccessor

@Component({
  ...
  providers: [{
    provide: NG_VALUE_ACCESSOR,
    useExisting: CustomTimeControl),
    multi: true
  }]
})
export class CustomTimeControl implements ControlValueAccessor {
  // implement ControlValueAccessor
}

自定义复合控件 dateTime ,实现ControlValueAccessor 验证器

@Component({
  ...
  providers: [{
    provide: NG_VALIDATORS,
    useExisting: CustomDateTimeControl,
    multi: true
  }, {
    provide: NG_VALUE_ACCESSOR,
    useExisting: CustomDateTimeControl,
    multi: true
  }]
})
export class CustomDateTimeControl implements OnInit, ControlValueAccessor, Validator {
  private propagateChange = function (change) { };
  private propagateTouched = function () { };

  // Inner controls (you can also use an internal FormGroup for this)
  public date = new FormControl();
  public time = new FormControl();

  constructor() {}

  ngOnInit() {
    this.date.valueChanges
      .subscribe(value => {
        this.propagateChange(value + ' ' + this.time.value);
        this.propagateTouched();
      }

    this.time.valueChanges
      .subscribe(value => {
        this.propagateChange(this.date.value + ' ' + value);
        this.propagateTouched();
      }
    }

  writeValue(value) {
    // Need to update the inner controls, but don't use setValue / patchValue,
    // as that will trigger valueChanges in the above subscriptions,
    // incorrectly calling touched
  }

  registerOnChange(fn) {
    this.propagateChange = fn;
  }

  registerOnTouched(fn) {
    this.propagateTouched = fn;
  }

  validate(control) {
    // Custom logic to validate the parent control. In this case,
    // we may choose to union all childrens' errors.

    let errors = Object.assign(this.localControl.errors || {}, this.remoteControl.errors || {});
    return Object.keys(errors).length ? errors : null;
  }
}

回答我自己的初始问题,在一系列复合控件中冒泡错误的好方法是在这些复合控件中实现Validator,并使其validate函数返回子控件错误的某种组合。

我希望这对其他人有用。