1. ホーム
  2. typescript

[解決済み] Angular 2 カスタムフォーム入力

2022-12-19 21:35:09

質問

ネイティブのコンポーネントと同じように動作するカスタムコンポーネントを作成するには、どうすればよいでしょうか。 <input> タグのように動作するカスタムコンポーネントを作成するにはどうしたらよいでしょうか。私はカスタムフォーム制御をngControl、ngForm、[(ngModel)]をサポートできるようにしたいです。

私の理解では、独自のフォームコントロールがネイティブのものと同じように動作するように、いくつかのインターフェイスを実装する必要があります。

また、ngFormディレクティブがバインドするのは <input> タグにのみバインドされるようですが、これは正しいのでしょうか?どのように私はそれに対処することができますか?


なぜこれが必要なのかを説明します。私は、いくつかの入力要素をラップして、1つの入力として一緒に動作できるようにしたいのです。これを扱う他の方法はありますか? もう一回。このコントロールは、ネイティブのものと同じようにしたいのです。バリデーション、ngForm、ngModelの双方向バインディング、その他。

ps: Typescriptを使っています。

どのように解決するのですか?

実際には、2つのことを実施する必要があります。

  • フォームコンポーネントのロジックを提供するコンポーネントです。これは、入力は ngModel 自体によって提供されるため、入力は必要ありません。
  • カスタム ControlValueAccessor との間のブリッジを実装します。 ngModel / ngControl

サンプルを見てみましょう。ある会社のタグのリストを管理するコンポーネントを実装したいと思います。このコンポーネントでは、タグの追加と削除を行うことができます。タグのリストが空でないことを確認するために、バリデーションを追加したいと思います。以下のようにコンポーネントに定義します。

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

見ての通り、このコンポーネントには入力はなく、代わりに setValue があります (名前はここでは重要ではありません)。これは後で ngModel からコンポーネントに値を提供するために使用します。このコンポーネントは、コンポーネントの状態(タグリスト)が更新されたときに通知するイベントを定義しています。

では、このコンポーネントと ngModel / ngControl . を実装したディレクティブに相当します。 ControlValueAccessor インタフェースを実装したディレクティブに対応します。この値へのアクセスのために、プロバイダを定義しなければなりません。 NG_VALUE_ACCESSOR トークンに対応するプロバイダを定義する必要があります (忘れずに forwardRef ディレクティブの後に定義されているので)。

このディレクティブは、イベントリスナーを tagsChange イベントのイベントリスナーを付けます (すなわち、ディレクティブが付けられているコンポーネント、すなわち TagsComponent ). そのため onChange メソッドは、イベントが発生したときに呼び出されます。このメソッドはAngular2が登録したものに相当します。こうすることで、変更を認識し、それに応じて関連するフォームコントロールを更新します。

このメソッドは writeValue で束縛された値が呼び出されます。 ngForm に束縛された値が更新されたときに呼び出されます。にアタッチされたコンポーネント(つまり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 を削除すると valid の属性は companyForm.controls.tags コントロールは false に自動的に変更されます。

詳しくはこちらの記事("NgModel対応コンポーネント"項)をご覧ください。