试想一下场景:
父组件只有点击事件:
点击按钮时或点击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样式
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;
}
}
最后附上样式,仅供参考
@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;
}