Angular 2自定义复合控件 [英] Angular 2 custom composite control

查看:72
本文介绍了Angular 2自定义复合控件的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm trying to create a custom composite control for Angular 2. My requirements are I need to create a generic file-picker control that allows users to select a file either using a html5 input[type=file] or by inputing a url to a file.

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

I've decided to create generic form controls that implement the ControlValueAccessor interface for a both the sub-controls, as they are going to be used elsewhere individually.

我正在尝试将它们都包装在 file-picker-local-or-remote (缺少更好的词)控件中.该外部控件应负责发出所选文件的内容,而不必关心文件的选择方式.

I'm trying to wrap them both inside a file-picker-local-or-remote (for lack of a better word) control. This outer control should be responsible for emitting the contents of the file chosen, without caring how the file was picked.

将值一直传播到消费形式很容易.每次子控件向中间控件传播值时,中间控件都会使用registerChange回调将其进一步向上传递到消费者链.

Propagating the value all the way to the consuming form is easy. Every time the subcontrols propagates a value to the intermediary control, the intermediary control relays it further up the chain to the consumer, using the registerChange callback.

但是,我在传播可能在子控件中发生的验证错误时遇到了麻烦.我需要将错误一直传播到使用表单,以便可以将其本地化.

However, I'm having trouble propagating validation errors that might occur in the subcontrols. I need to propagate the errors all the way to the consuming form so that they may be localised.

例如如果用户在remote-file-picker子控件中输入了无效的url,则该子控件的验证功能将触发并显示正确的错误.该错误提供给中间控件.如何将这个无效的URL错误一直传播到使用表单?

E.g. If a user enters an invalid url in the remote-file-picker subcontrol, that subcontrol's validation function fires with the correct error. This error is given to the intermediary control. How can I propagate this invalid-url error all the way to the consuming form?

更广泛地说,是否有关于如何在Angular 2中创建复合控件的特定指南?我找不到包装其他自定义控件的自定义控件的任何示例,因此我不确定自己是否正确执行了操作.

More broadly, is there specific guidance about how to create composite controls in Angular 2? I can't find any examples of custom controls that wrap other custom controls, so I'm not really sure I'm doing it correctly.

按不同的方式输入:

表格:

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>

文件选择器本地或远程

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

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

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

When the remote subcontrol fails validation, it gives its error codes to the innerForm. How do I propagate those error messages to the outer form, so that I can replace ******???****** with the appropriate validation message?

我应该注意到,有很多方法可以破解解决方案或绕过此问题,包括使用事件发射器构建我自己的解决方案,而不是首先使用复合控件,等等.

I should note that there are quite a fews ways for me to hack a solution or bypass this problem, including building my own solution using event emitters, not having a composite control in the first place, etc.

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

What I'm actually interested in is the Angular 2 way of creating reusable and extensible form controls that consumers can interact with just like any other form control, and can be further built upon by other developers to create even more high-level controls.

推荐答案

我和一位同事不久前就想出了这一点,但是这里是解决任何其他问题的人的解决方案.

A colleague and I figured this out a while ago but here is the solution for anyone else stumbling across this.

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

The key is to implement both ControlValueAccessor and Validator interfaces in the composite component.

例如

自定义日期控件,实现ControlValueAccessor

Custom date control, implements ControlValueAccessor

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

自定义时间控件,实现ControlValueAccessor

Custom time control, implements ControlValueAccessor

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

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

Custom composite control dateTime, implements both ControlValueAccessor and Validator

@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函数返回子控件错误的某种组合.

To my answer my own initial question, a good way to bubble errors up a chain of composite controls is to implement Validator in those composite controls, and have their validate function return some combination of the child controls' errors.

我希望这对其他人有用.

I hope this is useful for others.

这篇关于Angular 2自定义复合控件的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆