我一直在关注 angular-material 文档,该文档着眼于创建自定义表单字段控件: https ://material.angular.io/guide/creating-a-custom-form-field-control
它可以方便地跳过模板和反应形式的完整示例,因此我一直在争先恐后地尝试将其全部连接起来。
我已经尝试过了,取得了不同程度的成功。尽管还有其他问题,但我首先想了解如何让这个自定义字段识别它的时间invalid
,以便我可以执行<mat-error>
您在下面看到的操作(我删除了*ngIf
只是这样我可以看到 的状态invalid
)。{{symbolInput.invalid}}
总是,而false
实际上它应该true
是所需的字段!
自定义 MatFormFieldControl 模板使用:
<mat-form-field class="symbol">
<symbol-input
name="symbol"
placeholder="Symbol"
ngModel
#symbolInput="ngModel"
[(ngModel)]="symbol"
required></symbol-input>
<button
mat-button matSuffix mat-icon-button
*ngIf="symbol && (symbol.asset1 || symbol.asset2)"
aria-label="Clear"
(click)="clearSymbol()">
<mat-icon>close</mat-icon>
</button>
<mat-error >{{symbolInput.invalid}}</mat-error>
</mat-form-field>
自定义 MatFormFieldControl 类:
export interface AssetSymbol {
asset1: string, asset2: string
}
@Component({
selector: 'symbol-input',
templateUrl: './symbol-input.component.html',
styleUrls: ['./symbol-input.component.css'],
providers: [{ provide: MatFormFieldControl, useExisting: SymbolInputComponent}]
})
export class SymbolInputComponent implements MatFormFieldControl<AssetSymbol>, OnDestroy {
static nextId = 0;
stateChanges = new Subject<void>();
parts: FormGroup;
focused = false;
errorState = false;
controlType = 'symbol-input';
onChangeCallback;
@HostBinding() id = `symbol-input-${SymbolInputComponent.nextId++}`;
@HostBinding('class.floating')
get shouldLabelFloat() {
return this.focused || !this.empty;
}
@HostBinding('attr.aria-describedby')
describedBy = '';
setDescribedByIds(ids: string[]) {
this.describedBy = ids.join(' ');
}
get empty() {
let n = this.parts.value;
return !n.asset1 && !n.asset2;
}
@Input()
get value(): AssetSymbol | null {
let n = this.parts.value;
return { asset1: n.asset1, asset2: n.asset2};
}
set value(symbol: AssetSymbol | null) {
symbol = symbol || { asset1: "", asset2: ""};
this.parts.setValue({asset1: symbol.asset1, asset2: symbol.asset2});
this.stateChanges.next();
}
@Input()
get placeholder() {
return this._placeholder;
}
set placeholder(plh) {
this._placeholder = plh;
this.stateChanges.next();
}
private _placeholder: string;
@Input()
get required() {
return this._required;
}
set required(req) {
this._required = coerceBooleanProperty(req);
this.stateChanges.next();
}
private _required = false;
@Input()
get disabled() {
return this._disabled;
}
set disabled(dis) {
this._disabled = coerceBooleanProperty(dis);
this.stateChanges.next();
}
private _disabled = false;
constructor(
fb: FormBuilder,
@Optional() @Self() public ngControl: NgControl,
private fm: FocusMonitor,
private elRef: ElementRef<HTMLElement>) {
this.parts = fb.group({'asset1': '', 'asset2': ''});
// Setting the value accessor directly (instead of using
// the providers) to avoid running into a circular import.
if (this.ngControl != null) this.ngControl.valueAccessor = this;
fm.monitor(elRef.nativeElement, true).subscribe(origin => {
this.focused = !!origin;
this.stateChanges.next();
});
this.stateChanges.subscribe(() => {
this.expandInput(this.value.asset1.length);
if (this.onChangeCallback) {
this.onChangeCallback(this.value);
if (this.required) {
const symbol = this.value;
if (!symbol.asset1 || !symbol.asset2) {
this.errorState = true;
} else {
this.errorState = false;
}
}
}
});
}
onContainerClick(event: MouseEvent) {
if ((event.target as Element).tagName.toLowerCase() != 'input') {
this.elRef.nativeElement.querySelector('input').focus();
}
}
ngOnDestroy() {
this.stateChanges.complete();
this.fm.stopMonitoring(this.elRef.nativeElement);
}
onKeyup() {
this.stateChanges.next();
}
static ASSET1_INPUT_SIZE = 2;
asset1InputSize = SymbolInputComponent.ASSET1_INPUT_SIZE;
expandInput(currentSize) {
//const currentSize = (event.target as HTMLInputElement).value.length;
if (currentSize >= 3) {
this.asset1InputSize = currentSize;
} else {
this.asset1InputSize = SymbolInputComponent.ASSET1_INPUT_SIZE;
}
}
writeValue(value: any) {
this.value = value;
}
registerOnChange(fn: any) {
this.onChangeCallback = fn;
}
registerOnTouched(fn: any) {
}
}
符号输入.component.html:
<div [formGroup]="parts" >
<input class="asset asset1" formControlName="asset1" (keyup)="onKeyup()" [size]="asset1InputSize" maxlength="5">
<span class="input-spacer">⁄</span>
<input class="asset asset2" formControlName="asset2" size="6" maxlength="5">
</div>
有人会善意地指出我正确的方向吗?
**已更新**
symbolInput.invalid
标志现在在订阅this.ngControl.valueChanges
和设置后设置this.ngControl.control.setErrors
:
constructor(
fb: FormBuilder,
@Optional() @Self() public ngControl: NgControl,
private fm: FocusMonitor,
private elRef: ElementRef<HTMLElement>) {
this.parts = fb.group({'asset1': ['',[Validators.required]], 'asset2': ['',[Validators.required]]});
if (this.ngControl != null) this.ngControl.valueAccessor = this;
fm.monitor(elRef.nativeElement, true).subscribe(origin => {
this.focused = !!origin;
this.stateChanges.next();
});
this.ngControl.valueChanges.subscribe(()=>{
this.expandInput(this.value.asset1.length);
if (this.required) {
if (this.parts.invalid) {
this.errorState = true;
this.ngControl.control.setErrors({ "invalidSymbol": true });
} else {
this.errorState = false;
this.ngControl.control.setErrors(null);
}
}
});
this.stateChanges.subscribe(() => {
if (this.onChangeCallback) {
this.onChangeCallback(this.value);
}
});
}
如果您认为这可以改进,请告知。