背景:最近项目准备使用angular7,想快速入手,最好的方式就是自己动手封装一些常用的组件,在此可以直接参考primeng,重构一些常用的组件。
目的:封装一个checkbox组件
废话不多说,跟着下面的步骤,完整的跑通一个组件的开发。
<div
[ngStyle]="style"
[ngClass]="'app-chkbox app-widget'"
[class]="styleClass">
<div class="app-helper-hidden-accessible">
<input #cb type="checkbox"
[attr.id]="inputId"
[name]="name"
[value]="value"
[checked]="checked"
[disabled]="disabled"
(focus)="onFocus($event)"
(blur)="onBlur($event)"
[ngClass]="{'app-state-focus':focused}"
(change)="handleChange($event)"
[attr.tabindex]="tabindex">
div>
<div class="app-chkbox-box app-widget app-corner-all app-state-default"
(click)="onClick($event,cb,true)"
[ngClass]="{'app-state-active':checked,'app-state-disabled':disabled,'app-state-focus':focused}">
<span class="app-chkbox-icon app-clickable"
[ngClass]="{'pi pi-check':checked}">span>
div>
div>
<label
(click)="onClick($event,cb,true)"
[class]="labelStyleClass"
[ngClass]="{'app-chkbox-label': true, 'app-label-active':checked, 'app-label-disabled':disabled, 'app-label-focus':focused}"
*ngIf="label"
[attr.for]="inputId">{{label}}label>
$activeColor: #007ad9;
$normalColor: #a6a6a6;
$hoverColor: #212121;
.app-chkbox {
cursor: pointer;
display: inline-block;
vertical-align: middle;
margin-right: .25em;
user-select: none;
.app-chkbox-box {
border: 1px solid $normalColor;
background-color: #fff;
width: 20px;
height: 20px;
line-height: 20px;
border-radius: 2px;
text-align: center;
border-radius: 3px;
transition: background-color .2s, border-color .2s, box-shadow .2s;
&.app-state-active {
border-color: $activeColor;
background-color: $activeColor;
}
&:not(.app-state-disabled):hover {
border-color: $hoverColor;
}
}
.app-chkbox-icon {
display: block;
}
}
.app-chkbox-label {
vertical-align: middle;
}
基础样式,不够完整,用心的同学可以自行补全;
import { Component, OnInit, EventEmitter, ChangeDetectorRef, forwardRef, OnChanges, SimpleChanges, Input, Output } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR, FormControl } from '@angular/forms';
@Component({
selector: 'app-checkbox',
templateUrl: './checkbox.component.html',
styleUrls: ['./checkbox.component.scss'],
providers: [{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => CheckboxComponent),
multi: true
}]
})
export class CheckboxComponent implements OnInit, ControlValueAccessor {
@Input() name: string;
@Input() value: any;
@Input() label: string;
@Input() disabled: boolean;
@Input() binary: boolean;
@Input() tabindex: number;
@Input() inputId: string;
@Input() style: any;
@Input() styleClass: string;
@Input() labelStyleClass: string;
@Output() change: EventEmitter<any>;
formControl: FormControl;
checked: boolean;
focused: boolean;
model: any;
onModelChange: Function;
onModelTouched: Function;
constructor(
private cd: ChangeDetectorRef
) {
this.change = new EventEmitter<any>();
this.onModelChange = () => {};
this.onModelTouched = () => {};
this.focused = false;
this.checked = false;
}
ngOnInit() {
}
/**
* template模板 源事件
* @param event 组件
* @param cb 表单元素
* @param focus 触发focus事件
*/
onClick(event: Event, checkbox: HTMLInputElement, focus: boolean): void {
event.preventDefault();
if (this.disabled) {
return ;
}
this.checked = !this.checked;
this.updateModel();
if (focus) {
checkbox.focus();
}
}
/**
* 更新model
*/
updateModel() {
if (!this.binary) {
if (this.checked) {
this.addValue();
} else {
this.removeValue();
}
this.onModelChange(this.model); // model-to-ui
if (this.formControl) {
this.formControl.setValue(this.model); // 触发writeValue
}
} else {
this.onModelChange(this.checked);
}
this.change.emit(this.checked); // 外部获取的event事件
}
addValue() {
if (this.model) {
this.model = this.model.concat([this.value]);
} else {
this.model = [this.value]; // 第一次初始化
}
}
removeValue() {
this.model = this.model.filter((item) => {
return item !== this.value;
});
}
handleChange(event: any) {
this.checked = event.target.checked;
this.updateModel();
}
isChecked() {
if (this.binary) { // 允许返回boolean值
return this.model;
} else {
return this.model && this.model.indexOf(this.value) > -1;
}
}
// 触发更新-内部状态
onFocus(event) {
this.focused = true;
}
onBlur(event) {
this.focused = false;
this.onModelTouched();
}
// Angular API 和 DOM 元素之间的桥梁
writeValue(model) {
this.model = model;
this.checked = this.isChecked();
this.cd.markForCheck();
}
registerOnChange(fn: any) {
this.onModelChange = fn;
}
registerOnTouched(fn: any) {
this.onModelTouched = fn;
}
setDisabledState(val: boolean) {
this.disabled = val;
}
}
重点说明:
providers: [{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => CheckboxComponent),
multi: true
}]
组件类定义,providers声明,provide是必须的,Used to provide a ControlValueAccessor for form controls。
interface ControlValueAccessor {
writeValue(obj: any): void
registerOnChange(fn: any): void
registerOnTouched(fn: any): void
setDisabledState(isDisabled: boolean)?: void
}
Defines an interface that acts as a bridge between the Angular forms API and a native element in the DOM
定义一个接口,作为Angular表单API和DOM中的原生元素之间的桥梁
正是因为它,才实现了Angular ngModel及其form表单的接口
这里不多赘述,可以参考文档
官方文档地址
基础概念需要提前熟悉:EventEmitter,Input,Output, FormControl等等
组件类的方法遵循基本原则:
比如,单一性,拆得越细越好,一个方法就干一件事,便于复用:
更新值,创建 addValue
移除值, 创建removeValue
<form [formGroup]="myFormGroup" (ngSubmit)="onSubmit()">
<div>
<h3 class="first">Basich3>
<div class="ui-g" style="width:250px;margin-bottom:10px">
<div class="ui-g-12"><app-checkbox value="New York" [formControl]="myFormGroup.controls['checkList']" label="New York" [(ngModel)]="checkboxList" inputId="ny">app-checkbox>div>
<div class="ui-g-12"><app-checkbox value="San Francisco" [formControl]="myFormGroup.controls['checkList']" name="chbox" label="San Francisco" [(ngModel)]="checkboxList" inputId="sf">app-checkbox>div>
<div class="ui-g-12"><app-checkbox value="Los Angeles" [formControl]="myFormGroup.controls['checkList']" name="chbox" label="Los Angeles" [(ngModel)]="checkboxList" inputId="la">app-checkbox>div>
div>
<p [hidden]="myFormGroup.controls['checkList'].valid || (myFormGroup.controls['checkList'].pristine)" class="alert alert-danger">
这是一个必填项
p>
Selected Cities: <span *ngFor="let city of checkboxList" style="margin-right:10px">{{city}}span>
div>
<p>
Form Value: {{ myFormGroup.value | json }}
p>
<p>
Form Status: {{ myFormGroup.status }}
p>
<p>
<button type="button" [ngStyle]="{'margin-right': '5px'}" (click)="setNormalValue()">setNormalValuebutton>
<button type="submit" [disabled]="myFormGroup.invalid">Submitbutton>
p>
form>
import { Component, OnInit } from '@angular/core';
import { FormGroup, FormControl, Validators } from '@angular/forms';
@Component({
selector: 'app-doc',
templateUrl: './doc.component.html',
styleUrls: ['./doc.component.css']
})
export class DocComponent implements OnInit {
checkboxList: string[];
myFormGroup = new FormGroup({
checkList: new FormControl('', {
validators: Validators.required
})
});
constructor() { }
ngOnInit() {
this.checkboxList = [];
}
// public updateSwitchValue(check: boolean) {
// this.switchForm.controls.switchValue.setValue(!check);
// }
setNormalValue() {
this.checkboxList = ['New York', 'Los Angeles'];
}
onSubmit() {
console.log(this.switchForm);
}
}