【angular】 封装组件的整体思路和实现的全过程,完成一个radio表单组件

要实现的效果

【angular】 封装组件的整体思路和实现的全过程,完成一个radio表单组件_第1张图片

template模板

试想一下场景:
父组件只有点击事件:
点击按钮时或点击label时,都一个方法,handleClick事件;
属性和样式:
2个关键属性,disabled 和 checked,并根据值设置对应css样式;

<div
  [ngStyle]="style"
  [ngClass]="'app-radio app-widget'"
  [class]="styleClass">
  <div class="app-helper-hidden-accessible">
    <input #rb type="radio"
      [attr.id]="inputId"
      [attr.name]="name"
      [attr.value]="value"
      [checked]="checked"
      [disabled]="disabled"
      (focus)="onFocus($event)"
      (blur)="onBlur($event)"
      (change)="onChange($event)"
      [attr.tabindex]="tabindex">
    div>
    <div class="app-radio-box app-widget app-state-default"
      (click)="handleClick($event, rb)"
      [ngClass]="{'app-state-active':checked, 'app-state-disabled':disabled, 'app-state-focus':focused}">
      <span class="app-radio-icon app-clickable"
        [ngClass]="{'pi pi-circle-on':checked}">span>
  div>
div>
<label
  (click)="handleClick($event, rb)"
  [class]="labelStyleClass"
  [ngClass]="{'app-radio-label':true, 'app-label-active':checked, 'app-label-disabled':disabled, 'app-label-focus':focused}"
  *ngIf="label"
  [attr.for]="inputId">{{label}}label>

组件类

知识点:
@ViewChild(‘rb’) rb: ElementRef;
可以快速获取template中的DOM元素的引用,并进行相关操作;也可以通过方法把模板变量传给ts类;

业务逻辑

用户点击事件,元素checked状态更新,ts类属性checked状态更新,框架会自动触发模板css样式更新;
registerOnChange钩子触发,此时把ngModel的value值返回给ui模板视图,实现表单双向绑定;
点击的同时,手动触发focus方法,离开时触发blur方法时,registerOnTouched钩子触发,更新验证状态,
focused属性改变,同样内部同步更新模板css样式

依赖脏检查类,触发ui更新

private cd: ChangeDetectorRef
writeValue触发时,添加到标记脏检查视图中:
this.cd.markForCheck();

import { Component, OnInit, EventEmitter, ChangeDetectorRef, ViewChild, ElementRef, forwardRef, Input, Output } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';

@Component({
  selector: 'app-radio',
  templateUrl: './radio.component.html',
  styleUrls: ['./radio.component.scss'],
  providers: [{
    provide: NG_VALUE_ACCESSOR,
    useExisting: forwardRef(() => RadioComponent),
    multi: true
  }]
})
export class RadioComponent implements ControlValueAccessor {
  @Input() style: any;
  @Input() styleClass: string;
  @Input() labelStyleClass: string;
  @Input() tabindex: number;
  @Input() inputId: string;
  @Input() name: string;

  @Input() value: any;
  @Input() label: string;

  @Input() disabled: boolean;
  @Input() checked: boolean; // 是否选中
  focused: boolean;

  @Output() clicks: EventEmitter<any>; // onChange事件
  onModelChange: Function;
  onModelTouched: Function;

  @ViewChild('rb') rb: ElementRef;

  constructor(
    private cd: ChangeDetectorRef
  ) {
    this.disabled = false;
    this.checked = false;
    this.onModelChange = () => {};
    this.onModelTouched = () => {};
    this.clicks = new EventEmitter<any>();
  }

  get isEnable() {
    return !this.disabled;
  }

  /**
   * template模板 源事件
   */
  handleClick(event: Event, rb: HTMLInputElement): void {
    if (this.isEnable) {
      this.select();
      rb.focus();
    }
    event.preventDefault();
  }

  /**
   * label点击事件
   * ViewChild属性装饰器,获取DOM元素(模板与类的通信桥梁)
   */
  select() {
    if (this.isEnable) {
      this.rb.nativeElement.checked = true;
      this.checked = true;
      this.onModelChange(this.value); // model-ui视图
      this.clicks.emit(this.value);
    }
  }

  onFocus() {
    this.focused = true;
  }

  onBlur() {
    this.focused = false;
    this.onModelTouched();
  }

  onChange() {
    this.select();
  }

  writeValue(value: string): void {
    // ngModel值改变时更新checked状态
    this.checked = (value === this.value);
    this.cd.markForCheck();
  }

  registerOnChange(fn: Function): void {
    this.onModelChange = fn;
  }

  registerOnTouched(fn: Function): void {
    this.onModelTouched = fn;
  }

  setDisabledState(val: boolean): void {
    this.disabled = val;
  }

}

最后附上样式,仅供参考

css样式

@import "./../../scss/colors";

.app-radio {
  display:inline-block;
  cursor: pointer;
  vertical-align: middle;
  margin-right: .25em;
  user-select: none;
  .app-radio-box {
    position: relative;
    width: 1.5em;
    height: 1.5em;
    line-height: 1.5em;
    border: 1px solid $normalColor;
    border-radius: 100%;
    text-align: center;
    &::before {
      position: absolute;
      content: '';
      width: 50%;
      height: 50%;
      left: 25%;
      top: 25%;
      border-radius: 50%;
      background-color: #fff;
    }
    &.app-state-active{
      background-color: $activeColor;
    }
  }
}


.app-radio, .app-radio-label {
  vertical-align: middle;
}

你可能感兴趣的:(angular,ngModel,radio组件)