10
5

More than 5 years have passed since last update.

Angular2でバリデーションメッセージを内包したカスタムコントロールコンポーネントを作成する

Last updated at Posted at 2016-09-23

やりたいこと

  • カスタムコントロールをコンポーネント化したい
  • コンポーネントに対して model-based form を使いたい
  • バリデーション後のエラーメッセージ等をコンポーネント内に内包したい

カスタムコントロールを作るには

NgModelFormControlNameに対応するにはControlValueAccessorを実装すればいいっぽい。
以下の記事を参照した。
Custom Form Controls in Angular 2 by thoughtram

コンポーネント内からバリデーションエラーを検知する

普通に作ろうとすると、内部からFormControlerrorsオブジェクトにアクセスできず、何がバリデーションエラーになっているのかわからないため、メッセージの出しようがない。
結論としては、InjectorからNgControlを取り出せば可能のようだ。

最終的なコード

こんな感じになった

@Component({
  selector: 'custom-input',
  template: `
  <input type="text" [(ngModel)]="value" (blur)="onBlur()"/>
  <div *ngIf="ngControl && ngControl.invalid && (ngControl.touched || ngControl.dirty)" class="error">
    <span *ngIf="ngControl.errors.required">必須項目です。</span>
  </div>
  `,
  styles: [`
  :host {
    display: inline-block;
    position: relative;
  }
  .error {
      display: block;
      top: calc(100% + 12px);
      position: absolute;
      padding: .5em 1em .4em;
      background-color: #f66;
      border: 1px solid #f00;
      border-radius: 5px;
      color: #fff;
      z-index: 10;
  }
  .error:after, .error:before {
    content: '';
    position: absolute;
    bottom: 100%;
    left: 15px;
    border: solid transparent;
  }
  .error:after {
    margin-left: 1px;
    border-bottom-color: #f66;
    border-width: 7px;
  }
  .error:before {
    border-bottom-color: #f00;
    border-width: 8px;
  }
  `],
  providers: [{
    provide: NG_VALUE_ACCESSOR,
    useExisting: forwardRef(() => CustomInput),
    multi: true
  }]
})
export class CustomInput implements ControlValueAccessor, OnInit {
    private innerValue: any = '';
    private ngControl: NgControl;

    private onTouchedCallback = () => {};
    private onChangeCallback = (_: any) => {};

    constructor(private injector: Injector) { }

    ngOnInit(): void {
      this.ngControl = this.injector.get(NgControl);
    }

    get value(): any {
        return this.innerValue;
    };

    set value(v: any) {
        if (v !== this.innerValue) {
            this.innerValue = v;
            this.onChangeCallback(v);
        }
    }

    onBlur() { this.onTouchedCallback() }

    writeValue(value: any) {
        if (value !== this.innerValue) {
            this.innerValue = value;
        }
    }
    registerOnChange(fn: any) { this.onChangeCallback = fn }
    registerOnTouched(fn: any) { this.onTouchedCallback = fn }
}

10
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
5