[Angular] ラジオボタン(radio)のカスタムコンポーネント[サンプルコード]


ラジオボタン(radios)の CSSをカスタムして、それをカスタムコンポーネントにしました。
意外と ([ngModel]) をカスタムコンポーネントでやっているリファレンスって少ないので、 ngModel の例としても役に立つかもしれない。

利用する側のコンポーネントのコード

parent.ts
@Component({
  selector: 'app-parent',
  templateUrl: './parent.component.html'
})
export class ParentComponent {
  @Input() gender: string;
}
parent.component.html
<app-radio selectValue="male" label="男" name="gender" [(ngModel)]="gender">
<app-radio selectValue="female" label="女" name="gender" [(ngModel)]="gender">
<app-radio selectValue="unknown" label="どちらでもない" name="gender" [(ngModel)]="gender">

コンポーネントのコード

radio.component.ts
import { Component, forwardRef, Input } from '@angular/core';
import { NG_VALUE_ACCESSOR } from '@angular/forms';

@Component({
  selector: 'app-radio',
  templateUrl: './radio.component.html',
  styleUrls: ['./radio.component.scss'],
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      multi: true,
      useExisting: forwardRef(() => RadioComponent),
    },
  ],
})
export class RadioComponent {
  _value: string;
  @Input() selectValue: any;
  @Input() label: string;

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

  get value(): string {
    return this._value;
  }
  @Input('value')
  set value(text: string) {
    if (this._value !== text) {
      this._value = text;
      this.onChangeCallback(text);
    }
  }

  writeValue(text: string): void {
    if (text !== this.value) {
      this.value = text;
    }
  }

  registerOnChange(fn: any): void {
    this.onChangeCallback = fn;
  }

  registerOnTouched(fn: any): void {
    this.onTouchedCallback = fn;
  }

  setDisabledState(isDisabled: boolean): void {
  }

}
radio.component.html
<label class="radio-wrapper">
  <input type="radio" [value]="selectValue" [(ngModel)]="value" >
  <span class="checkmark"></span><span class="label">{{ label }}</span>
</label>
radio.component.scss
.radio-wrapper {
  display: inline-flex;
  align-items: center;
  position: relative;
  margin-top: 8px;
  cursor: pointer;
  font-size: 16px;
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;

  &.margin-left {
    margin-left: 34px;
  }
  .label {
    margin-left: 4px;
  }
}

/* Hide the browser's default radio button */
.radio-wrapper input {
  position: absolute;
  opacity: 0;
}

/* Create a custom radio button */
.checkmark {
  display: inline-block;
  //position: absolute;
  //top: 0;
  //left: 0;
  height: 21px;
  width: 21px;
  background-color: #eee;
  border-radius: 50%;
}

/* On mouse-over, add a grey background color */
.radio-wrapper:hover input ~ .checkmark {
  background-color: #ccc;
}

/* When the radio button is checked, add a blue background */
.radio-wrapper input:checked ~ .checkmark {
  background-color: white;
  border: 1px solid #2196F3;
}

/* Create the indicator (the dot/circle - hidden when not checked) */
.checkmark:after {
  content: "";
  position: absolute;
  display: none;
}

/* Show the indicator (dot/circle) when checked */
.radio-wrapper input:checked ~ .checkmark:after {
  display: block;
}

/* Style the indicator (dot/circle) */
.radio-wrapper .checkmark:after {
  top: 7px;
  left: 7px;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: #2196F3;
}