Angular 2 自定义表单输入 [英] Angular 2 custom form input

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

问题描述

如何创建可以像原生 <input> 标签一样工作的自定义组件?我想让我的自定义表单控件能够支持 ngControl、ngForm、[(ngModel)].

据我所知,我需要实现一些接口来使我自己的表单控件像原生控件一样工作.

另外,好像 ngForm 指令只绑定 <input> 标签,对吗?我该如何处理?

<小时>

让我解释一下为什么我需要这个.我想包装几个输入元素,使它们能够作为一个输入一起工作.有其他方法可以处理吗?再一次:我想让这个控件就像原生控件一样.验证、ngForm、ngModel 双向绑定等.

ps:我使用打字稿.

解决方案

其实有两件事要实现:

  • 提供表单组件逻辑的组件.它不需要输入,因为它将由 ngModel 本身提供
  • 一个自定义的 ControlValueAccessor 将实现这个组件和 ngModel/ngControl
  • 之间的桥梁

让我们举个例子.我想实现一个管理公司标签列表的组件.该组件将允许添加和删除标签.我想添加验证以确保标签列表不为空.我将在我的组件中定义它,如下所述:

(...)从'./app.tags.ngform'导入{TagsComponent};从'./app.tags.ngform.accessor'导入{TagsValueAccessor};功能 notEmpty(控制){if(control.value == null || control.value.length===0) {返回 {非空:真}}返回空;}@成分({选择器:'公司详细信息',指令:[ FormFieldComponent, TagsComponent, TagsValueAccessor ],模板:`<form [ngFormModel]=companyForm">名称:<input [(ngModel)]="company.name";[ngFormControl]=companyForm.controls.name"/>标签:<tags [(ngModel)]=company.tags";[ngFormControl]=companyForm.controls.tags"></tags></表单>`})导出类 DetailsComponent 实现 OnInit {构造函数(_builder:FormBuilder){this.company = new Company('companyid','一些名字', [ 'tag1', 'tag2' ]);this.companyForm = _builder.group({名称:['', Validators.required],标签: ['', notEmpty]});}}

TagsComponent 组件定义了在 tags 列表中添加和删除元素的逻辑.

@Component({选择器:'标签',模板:`<div *ngIf=标签"><span *ngFor="#tag of tags";样式=字体大小:14px";class="label label-default";(click)="removeTag(tag)">{{label}} <span class="glyphicon glyphicon-remove";aria-hidden="true"></span></span><span>&nbsp;|&nbsp;</span><span style="display:inline-block;"><input [(ngModel)]="tagToAdd";样式=宽度:50px;字体大小:14px;"类=自定义"/><em class="glyphicon glyphicon-ok";咏叹调隐藏=真";(click)=addTag(tagToAdd)"></em></span>

`})导出类标签组件{@输出()tagsChange: EventEmitter;构造函数(){this.tagsChange = new EventEmitter();}设置值(值){this.tags = 值;}移除标签(标签:字符串){var index = this.tags.indexOf(tag, 0);如果(索引!=未定义){this.tags.splice(index, 1);this.tagsChange.emit(this.tags);}}添加标签(标签:字符串){this.tags.push(this.tagToAdd);this.tagsChange.emit(this.tags);this.tagToAdd = '';}}

如您所见,该组件中没有输入,只有一个 setValue(名称在这里并不重要).我们稍后使用它来将 ngModel 中的值提供给组件.该组件定义了一个事件,用于在组件的状态(标签列表)更新时进行通知.

现在让我们实现这个组件和 ngModel/ngControl 之间的链接.这对应于实现 ControlValueAccessor 接口的指令.必须针对 NG_VALUE_ACCESSOR 标记为此值访问器定义提供者(不要忘记使用 forwardRef,因为指令是在之后定义的).

指令将在宿主的 tagsChange 事件上附加一个事件监听器(即指令附加的组件,即 TagsComponent).onChange 方法会在事件发生时被调用.这个方法对应的是Angular2注册的那个.这样它就会知道变化并相应地更新关联的表单控件.

writeValuengForm 中绑定的值被更新时被调用.在注入附加的组件(即TagsComponent)后,我们将能够调用它来传递这个值(参见前面的setValue 方法).

不要忘记在指令的绑定中提供CUSTOM_VALUE_ACCESSOR.

这里是自定义ControlValueAccessor的完整代码:

从'./app.tags.ngform'导入{TagsComponent};const CUSTOM_VALUE_ACCESSOR = CONST_EXPR(新提供者(NG_VALUE_ACCESSOR, {useExisting: forwardRef(() => TagsValueAccessor), multi: true}));@指示({选择器:'标签',主持人:{'(tagsChange)':'onChange($event)'},提供者:[CUSTOM_VALUE_ACCESSOR]})导出类 TagsValueAccessor 实现 ControlValueAccessor {onChange = (_) =>{};onTouched = () =>{};构造函数(私有主机:TagsComponent){}writeValue(value: any): void {this.host.setValue(value);}registerOnChange(fn: (_: any) => void): void { this.onChange = fn;}registerOnTouched(fn: () => void): void { this.onTouched = fn;}}

这样当我删除公司的所有tags时,companyForm.controls.tags控件的valid属性就变成了false 自动.

有关更多详细信息,请参阅这篇文章(NgModel 兼容组件"部分):

How can I create custom component which would work just like native <input> tag? I want to make my custom form control be able to support ngControl, ngForm, [(ngModel)].

As I understand, I need to implement some interfaces to make my own form control work just like native one.

Also, seems like ngForm directive binds only for <input> tag, is this right? How can i deal with that?


Let me explain why I need this at all. I want to wrap several input elements to make them able to work together as one single input. Is there other way to deal with that? One more time: I want to make this control just like native one. Validation, ngForm, ngModel two way binding and other.

ps: I use Typescript.

解决方案

In fact, there are two things to implement:

  • A component that provides the logic of your form component. It doesn't need an input since it will be provided by ngModel itself
  • A custom ControlValueAccessor that will implement the bridge between this component and ngModel / ngControl

Let's take a sample. I want to implement a component that manages a list of tags for a company. The component will allow to add and remove tags. I want to add a validation to ensure that the tags list isn't empty. I will define it in my component as described below:

(...)
import {TagsComponent} from './app.tags.ngform';
import {TagsValueAccessor} from './app.tags.ngform.accessor';

function notEmpty(control) {
  if(control.value == null || control.value.length===0) {
    return {
      notEmpty: true
    }
  }

  return null;
}

@Component({
  selector: 'company-details',
  directives: [ FormFieldComponent, TagsComponent, TagsValueAccessor ],
  template: `
    <form [ngFormModel]="companyForm">
      Name: <input [(ngModel)]="company.name"
         [ngFormControl]="companyForm.controls.name"/>
      Tags: <tags [(ngModel)]="company.tags" 
         [ngFormControl]="companyForm.controls.tags"></tags>
    </form>
  `
})
export class DetailsComponent implements OnInit {
  constructor(_builder:FormBuilder) {
    this.company = new Company('companyid',
            'some name', [ 'tag1', 'tag2' ]);
    this.companyForm = _builder.group({
       name: ['', Validators.required],
       tags: ['', notEmpty]
    });
  }
}

The TagsComponent component defines the logic to add and remove elements in the tags list.

@Component({
  selector: 'tags',
  template: `
    <div *ngIf="tags">
      <span *ngFor="#tag of tags" style="font-size:14px"
         class="label label-default" (click)="removeTag(tag)">
        {{label}} <span class="glyphicon glyphicon-remove"
                        aria-  hidden="true"></span>
      </span>
      <span>&nbsp;|&nbsp;</span>
      <span style="display:inline-block;">
        <input [(ngModel)]="tagToAdd"
           style="width: 50px; font-size: 14px;" class="custom"/>
        <em class="glyphicon glyphicon-ok" aria-hidden="true" 
            (click)="addTag(tagToAdd)"></em>
      </span>
    </div>
  `
})
export class TagsComponent {
  @Output()
  tagsChange: EventEmitter;

  constructor() {
    this.tagsChange = new EventEmitter();
  }

  setValue(value) {
    this.tags = value;
  }

  removeLabel(tag:string) {
    var index = this.tags.indexOf(tag, 0);
    if (index != undefined) {
      this.tags.splice(index, 1);
      this.tagsChange.emit(this.tags);
    }
  }

  addLabel(label:string) {
    this.tags.push(this.tagToAdd);
    this.tagsChange.emit(this.tags);
    this.tagToAdd = '';
  }
}

As you can see, there is no input in this component but a setValue one (the name isn't important here). We use it later to provide the value from the ngModel to the component. This component defines an event to notify when the state of the component (the tags list) is updated.

Let's implement now the link between this component and ngModel / ngControl. This corresponds to a directive that implements the ControlValueAccessor interface. A provider must be defined for this value accessor against the NG_VALUE_ACCESSOR token (don't forget to use forwardRef since the directive is defined after).

The directive will attach an event listener on the tagsChange event of the host (i.e. the component the directive is attached on, i.e. the TagsComponent). The onChange method will be called when the event occurs. This method corresponds to the one registered by Angular2. This way it will be aware of changes and updates accordingly the associated form control.

The writeValue is called when the value bound in the ngForm is updated. After having injected the component attached on (i.e. TagsComponent), we will be able to call it to pass this value (see the previous setValue method).

Don't forget to provide the CUSTOM_VALUE_ACCESSOR in the bindings of the directive.

Here is the complete code of the custom ControlValueAccessor:

import {TagsComponent} from './app.tags.ngform';

const CUSTOM_VALUE_ACCESSOR = CONST_EXPR(new Provider(
  NG_VALUE_ACCESSOR, {useExisting: forwardRef(() => TagsValueAccessor), multi: true}));

@Directive({
  selector: 'tags',
  host: {'(tagsChange)': 'onChange($event)'},
  providers: [CUSTOM_VALUE_ACCESSOR]
})
export class TagsValueAccessor implements ControlValueAccessor {
  onChange = (_) => {};
  onTouched = () => {};

  constructor(private host: TagsComponent) { }

  writeValue(value: any): void {
    this.host.setValue(value);
  }

  registerOnChange(fn: (_: any) => void): void { this.onChange = fn; }
  registerOnTouched(fn: () => void): void { this.onTouched = fn; }
}

This way when I remove all the tags of the company, the valid attribute of the companyForm.controls.tags control becomes false automatically.

See this article (section "NgModel-compatible component") for more details:

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

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