背景:最近項目準備使用angular7,想快速入手,最好的方式就是自己動手封裝一些常用的元件,在此可以直接參考primeng,重構一些常用的元件。
目的:封裝一個checkbox元件
廢話不多說,跟着下面的步驟,完整的跑通一個元件的開發。
checkbox.component.html
<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>
- 注解:
- 輸入屬性 @Input():根據需求配置;
- ngClass控制樣式的顯示和移除;
- 事件(eventName):重點就是onClick事件;
- HTML attribute 和 DOM property的差別;
checkbox.component.scss
$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;
}
基礎樣式,不夠完整,用心的同學可以自行補全;
checkbox.component.ts
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
調用元件測試效果
doc.component.html
<form [formGroup]="myFormGroup" (ngSubmit)="onSubmit()">
<div>
<h3 class="first">Basic</h3>
<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()">setNormalValue</button>
<button type="submit" [disabled]="myFormGroup.invalid">Submit</button>
</p>
</form>
- myFormGroup 檢測,整個表單,可以把這個對象列印出來看結構;
- 驗證基礎要懂,幾個常用的狀态值: valid, invalid, pending, pristine, dirty , untouched, touched;
doc.component.ts
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);
}
}
- validators基礎要懂點;檢視文檔位址: 更多validators說明點我
- FormGroup,可以檢視文檔,響應式表單一章,就夠了;
- 這裡沒有自定義驗證規則,沒有異步驗證,文檔都有示例參考,檢視更多