我怎样才能创建像native <input> tag一样工作的自定义组件?我想让我的自定义表单控件能够支持ngControl,ngForm,[(ngModel)].

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

而且,似乎ngForm指令只绑定了<input>个标签,对吗?我该怎么处理?


让我解释一下为什么我需要这个.我想包装几个输入元素,使它们能够作为一个输入一起工作.还有别的办法吗?

附言:我使用打字脚本.

推荐答案

事实上,有两件事需要实施:

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

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

(...)
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]
    });
  }
}

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

@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 !== -1) {
      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 = '';
  }
}

正如你所见,这个名字里没有一个重要的输入.我们稍后使用它来提供从ngModel到组件的值.该组件定义了一个事件,在组件状态(标记列表)更新时通知.

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

该指令将在主机的tagsChange事件(即该指令所附加的组件,即TagsComponent)上附加一个事件侦听器.事件发生时将调用onChange方法.此方法与Angular2注册的方法相对应.这样,它将了解相关表单控件的更改并相应地更新.

ngForm中绑定的值更新时,调用writeValue.在注入附加的组件(即TagsComponent)后,我们将能够调用它来传递该值(请参阅前面的setValue方法).

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

以下是定制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; }
}

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

有关更多详细信息,请参阅本文("NgModel兼容组件"一节):

Typescript相关问答推荐

Typescript问题和缩减器状态不会在单击时添加用途.属性'状态和调度'不存在于userContextType类型上'|null'

错误:note_modules/@types/note/globals.d.ts:72:13 -错误TS 2403:后续变量声明必须具有相同的类型

如何基于对象关键字派生类型?

如果我有对象的Typescript类型,如何使用其值的类型?

防止获取发出不需要的请求

如何提取密钥及其对应的属性类型,以供在新类型中使用?

是否使用非显式名称隔离在对象属性上声明的接口的内部类型?

嵌套对象的类型

对于始终仅在不是对象属性时才抛出的函数,返回Never

如何将别名添加到vitest配置文件?

剧作家元素发现,但继续显示为隐藏

我可以使用typeof来强制执行某些字符串吗

如何从一个泛型类型推断多个类型

基于未定义属性的条件属性

TypeScrip:使用Cypress测试包含插槽的Vue3组件

编剧- Select 下拉框

如何在TypeScript中描述和实现包含特定属性的函数?

换行TypeScript';s具有泛型类型的推断数据类型

如何使用 AWS CDK 扩展默认 ALB 控制器策略?

如何确定单元格中的块对 mat-h​​eader-cell 的粘附(粘性)?