我最近花了几个小时浏览了很多资源,但似乎没有一个能像预期的那样开箱即用,所以我尽我所能在 stackblitz 上编写了这个示例。
有人可以告诉我这是否是完成以下任务的最佳方法?
链接到stackblitz: https://stackblitz.com/edit/angular-mat-reactive-form-control-ddssy1
自定义控件 Html:
<mat-form-field appearance="outline" [floatLabel]="'always'" class="example-full-width">
<mat-label>{{label}}</mat-label>
<input matInput [id]="id" #input [formControl]="control" [placeholder]="placeholder"/>
<mat-hint>Required</mat-hint>
<mat-error>{{errorMessage}}</mat-error>
</mat-form-field>
自定义控件 TS:
import { coerceBooleanProperty } from "@angular/cdk/coercion";
import {
Component,
ViewChild,
HostBinding,
Input,
ChangeDetectionStrategy,
Optional,
Self,
DoCheck,
OnInit,
} from "@angular/core";
import {
ControlValueAccessor,
NgControl,
FormControlName,
FormControl,
} from "@angular/forms";
import {
MatFormFieldControl,
ErrorStateMatcher, MatInput
} from "@angular/material";
import { Subject } from "rxjs";
export class MyErrorStateMatcher implements ErrorStateMatcher {
isErrorState(control: FormControl | null): boolean {
return !!(control && control.invalid && (control.dirty || control.touched));
}
}
@Component({
host: {
'(focusout)': 'onTouch()',
"[id]": "id",
"[attr.aria-describedby]": "describedBy"
},
selector: "custom-input",
templateUrl: "./custom-select.component.html",
styleUrls: ["./custom-select.component.scss"],
providers: [
{
provide: MatFormFieldControl,
useExisting: CustomSelectComponent
}
],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class CustomSelectComponent
implements ControlValueAccessor, OnInit, DoCheck {
static nextId = 0;
@HostBinding() id = `input-${CustomSelectComponent.nextId++}`;
@HostBinding("attr.aria-describedby") describedBy = "";
@ViewChild("Input") input: MatInput;
@Input() placeholder: string;
@Input() label: string;
@Input() disabled: boolean;
@Input('value') _value: any
get value() {
return this._value || null;
}
set value(val) {
this._value = val;
}
public control: FormControl;
public errorMessage: string;
get errorState(){
console.log('error state!');
return this.errorMatcher.isErrorState(this.ngControl.control as FormControl, null);
}
onChange: (value: any) => void;
onTouch: () => void;
constructor(
@Optional() @Self() public ngControl: NgControl,
@Optional() private _controlName: FormControlName,
private errorMatcher: ErrorStateMatcher,
) {
if (ngControl) {
ngControl.valueAccessor = this;
}
}
ngOnInit(): void {
this.control = this._controlName.control;
this.control.valueChanges.subscribe(res=>{
if(res){
this.validate();
}
})
this.control.markAsTouched();
this.validate();
}
ngDoCheck(): void {
if(this.control){
this.validate();
}
}
writeValue(obj: any): void {
this._value = obj;
}
registerOnChange(fn: any): void {
this.onChange = fn;
}
registerOnTouched(fn: any): void {
this.onTouch = fn;
}
validate(){
console.log(this.control);
this.errorMessage = null;
if(this.control.errors && this.control.errors.required && !this.control.value){
this.errorMessage = "Required";
return;
}
if(this.control?.value?.length < 3){
this.control.setErrors({ invalid: true});
this.errorMessage = 'Length must be at least 3 characters.';
return
}
}
}
父 HTML:
<div style="text-align:center">
<form class="example-form" [formGroup]="myForm" (submit)="submitForm()">
<custom-input placeholder="Favorite Food" label="Food" formControlName="food" [required]="true"></custom-input>
<button>Submit</button>
</form>
</div>
<div>
Form is valid? {{myForm.valid}}
</div>
家长 TS:
import { coerceBooleanProperty } from "@angular/cdk/coercion";
import {
Component,
ViewChild,
HostBinding,
Input,
ChangeDetectionStrategy,
Optional,
Self,
DoCheck,
OnInit,
} from "@angular/core";
import {
ControlValueAccessor,
NgControl,
FormControlName,
FormControl,
} from "@angular/forms";
import {
MatFormFieldControl,
ErrorStateMatcher, MatInput
} from "@angular/material";
import { Subject } from "rxjs";
export class MyErrorStateMatcher implements ErrorStateMatcher {
isErrorState(control: FormControl | null): boolean {
return !!(control && control.invalid && (control.dirty || control.touched));
}
}
@Component({
host: {
'(focusout)': 'onTouch()',
"[id]": "id",
"[attr.aria-describedby]": "describedBy"
},
selector: "custom-input",
templateUrl: "./custom-select.component.html",
styleUrls: ["./custom-select.component.scss"],
providers: [
{
provide: MatFormFieldControl,
useExisting: CustomSelectComponent
}
],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class CustomSelectComponent
implements ControlValueAccessor, OnInit, DoCheck {
static nextId = 0;
@HostBinding() id = `input-${CustomSelectComponent.nextId++}`;
@HostBinding("attr.aria-describedby") describedBy = "";
@ViewChild("Input") input: MatInput;
@Input() placeholder: string;
@Input() label: string;
@Input() disabled: boolean;
@Input('value') _value: any
get value() {
return this._value || null;
}
set value(val) {
this._value = val;
}
public control: FormControl;
public errorMessage: string;
get errorState(){
console.log('error state!');
return this.errorMatcher.isErrorState(this.ngControl.control as FormControl, null);
}
onChange: (value: any) => void;
onTouch: () => void;
constructor(
@Optional() @Self() public ngControl: NgControl,
@Optional() private _controlName: FormControlName,
private errorMatcher: ErrorStateMatcher,
) {
if (ngControl) {
ngControl.valueAccessor = this;
}
}
ngOnInit(): void {
this.control = this._controlName.control;
this.control.valueChanges.subscribe(res=>{
if(res){
this.validate();
}
})
this.control.markAsTouched();
this.validate();
}
ngDoCheck(): void {
if(this.control){
this.validate();
}
}
writeValue(obj: any): void {
this._value = obj;
}
registerOnChange(fn: any): void {
this.onChange = fn;
}
registerOnTouched(fn: any): void {
this.onTouch = fn;
}
validate(){
console.log(this.control);
this.errorMessage = null;
if(this.control.errors && this.control.errors.required && !this.control.value){
this.errorMessage = "Required";
return;
}
if(this.control?.value?.length < 3){
this.control.setErrors({ invalid: true});
this.errorMessage = 'Length must be at least 3 characters.';
return
}
}
}
答案 0 :(得分:0)
你所做的很好,但你可能还想添加这些:
/** Adding these just to update the component a bit */
@Input() name: string;
@Input() readOnly: boolean;
@Input() type: string;
@Input() required: boolean;
@Input() maxLength: number;
@Input() hint: string;
@Input() errMessage: string;
@Output() blur: EventEmitter<any> = new EventEmitter<any>();
onBlur(event) {
if (event && event.target && event.target.value) {
this.value = event.target.value;
this.blur.emit(event);
}
}
在 HTML 中,您可以像这样绑定它们:
<input
matInput
[id]="id"
#input
[formControl]="control"
[placeholder]="placeholder"
[name]="formControlName"
[readonly]="readOnly"
[type]="type"
[required]="required"
[maxLength]="maxLength"
(blur)="onBlur($event)"
/>
<mat-hint>{{ hint ? hint : 'Required' }}</mat-hint>
<mat-error>{{ errMessage ? errMessage : errorMessage }}</mat-error>