angular select2源码解析_Angular 组件库 NG-NEST 源码解析:Switch 组件

前言

NG-NEST介绍

今天我们来看一个表单组件 Switch 开关是怎么实现的。主要是对自定义表单组件中需要用到 ControlValueAccessor 这个的说明。

一般实现一个自定义的表单组件我们需要2个步骤,注册 NG_VALUE_ACCESSOR提供者,实现 ControlValueAccessor 接口

e97a981461f53686b70327d70171a375.gif

功能分析

  • 点击启用,再次点击关闭,根据状态展示不同的样式
  • 可以通过 [(ngModel)] 双向绑定值
  • 包含标签的不同布局方式
  • 禁用样式

代码实现

先看文件 lib/ng-nest/ui/switch/switch.component.html :

<div 
  #switch 
  class="x-switch" 
  [class.x-flex]="justify || align || direction" 
  [class.x-checked]="value" 
  [class.x-disabled]="disabled"
>
  <label *ngIf="label" [style.width]="labelWidth" [ngClass]="labelMap">{{ label }}</label>
  <div class="x-switch-row">
    <div class="x-switch-slider" (click)="switchClick()"></div>
  </div>
</div>

这个比较简单,根据不同参数绑定了不同的样式,重点在对应的 switch.component.ts 文件:

@Component({
  selector: `${XSwitchPrefix}`,
  templateUrl: './switch.component.html',
  styleUrls: ['./switch.component.scss'],
  encapsulation: ViewEncapsulation.None,
  changeDetection: ChangeDetectionStrategy.OnPush,
  providers: [XValueAccessor(XSwitchComponent)]
})
export class XSwitchComponent extends XSwitchProperty implements OnInit {
  @ViewChild('switch', { static: true }) switch: ElementRef;

  writeValue(value: any) {
    this.value = value;
    this.cdr.detectChanges();
  }

  constructor(public renderer: Renderer2, private cdr: ChangeDetectorRef, public configService: XConfigService) {
    super();
  }

  ngOnInit() {
    this.setFlex(this.switch.nativeElement, this.renderer, this.justify, this.align, this.direction);
    this.setClassMap();
  }

  setClassMap() {
    XClearClass(this.labelMap);
    this.labelMap[`x-text-align-${this.labelAlign}`] = this.labelAlign ? true : false;
  }

  switchClick() {
    if (this.disabled) return;
    this.value = !this.value;
    if (this.onChange) this.onChange(this.value);
    this.cdr.detectChanges();
  }

  formControlChanges() {
    this.ngOnInit();
    this.cdr.detectChanges();
  }
}

从上往下一个一个来说明

  • XValueAccessor 这个是对 NG_VALUE_ACCESSOR 的封装,用来在此组件中注册 NG_VALUE_ACCESSOR 提供者,实际返回的就是如下的代码:
{ 
  provide: NG_VALUE_ACCESSOR, 
  useExisting: forwardRef(() => XSwitchComponent), 
  multi: true 
}
  • 在 XSwitchProperty 这个属性类里面继承了 XControlValueAccessor 这个类,然后 XControlValueAccessor 这个里面又实现了 ControlValueAccessor 这个接口,此处有点绕,加上上面的 XValueAccessor 这个,刚好完成了我们最初说的2个步骤:
/**
 * 抽象泛型类,并实现 ControlValueAccessor 接口
 * 提供通用的基础属性以及功能
 */
export abstract class XControlValueAccessor<T> extends XFormProp implements ControlValueAccessor {
  // 组件值验证,主要是空值以及正则验证
  get invalid() {
    return !XIsEmpty(this.value) && this.invalidPattern;
  }
  // 正则验证,单个以及多个,对应的 message 也可设置成多个
  get invalidPattern(): boolean {
    let result = false;
    let index = 0;
    if (Array.isArray(this.pattern)) {
      for (const pt of this.pattern) {
        result = !new RegExp(pt).test(this.value as any);
        if (result) {
          this.invalidIndex = index;
          break;
        }
        index++;
      }
    } else {
      result = !new RegExp(this.pattern).test(this.value as any);
    }
    return result;
  }
  // 必填验证
  get requiredIsEmpty() {
    return this.required && XIsEmpty(this.value);
  }
  // 验证信息
  get invalidMessage(): string {
    if (Array.isArray(this.message)) {
      return this.message.length > this.invalidIndex ? this.message[this.invalidIndex] : '';
    } else {
      return this.message;
    }
  }
  invalidIndex: number = 0;
  labelMap: XClassMap = {};

  // 接口 ControlValueAccessor 需要实现的功能---start
  value: T;
  onChange: (value: T) => void;
  onTouched: () => void;
  writeValue(value: T): void {
    this.value = value;
  }
  registerOnChange(fn: (value: T) => void): void {
    this.onChange = fn;
  }
  registerOnTouched(fn: () => void): void {
    this.onTouched = fn;
  }
  setDisabledState(disabled: boolean) {
    this.disabled = disabled;
  }
  // 接口 ControlValueAccessor 需要实现的功能---end

  // 设置标签布局
  setFlex(ele: Element, renderer: Renderer2, justify: XJustify, align: XAlign, direction: XDirection) {
    if (!XIsEmpty(justify)) renderer.addClass(ele, `x-justify-${this.justify}`);
    if (!XIsEmpty(align)) renderer.addClass(ele, `x-align-${this.align}`);
    if (!XIsEmpty(direction)) renderer.addClass(ele, `x-direction-${this.direction}`);
  }
}
  • 接下来看 writeValue 方法,可以理解为对 XControlValueAccessor 中 writeValue方法的重写,使用 ngModel 设置初始或改变值的时候触发。
  • 其它方法 setClassMap 样式设置,switchClick 切换点击事件。
  • formControlChanges 这个用来在 Form 组件中做初始化。

总结

Switch 开关的功能比较简单,代码很少。主要说明库中是如何做自定义表单组件的,通过再次封装官方提供的接口到抽象类中,实际的组件只需要继承抽象类就可以了。在组件库中目前已有 16 种表单组件,通过此方法有效减少了重复代码。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值