我一直在关注有关创建自定义表单字段控件的angular-material文档: https://material.angular.io/guide/creating-a-custom-form-field-control
它方便地跳过了模板和反应形式的完整示例,因此我一直在争先恐后地尝试将其全部连接起来。
我受到了刺痛,并取得了不同的成功。尽管还有其他问题,但我首先想了解如何使此自定义字段在false
时可以识别,以便执行下面看到的invalid
(我删除了{{ 1}},以便可以看到<mat-error>
的状态)。 *ngIf
始终为invalid
,但实际上该字段应为{{symbolInput.invalid}}
,因为该字段是必填字段!
自定义MatFormFieldControl模板的用法:
false
自定义MatFormFieldControl类:
true
symbol-input.component.html:
<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>
有人会友好地将我指向正确的方向吗?
** 已更新 **
订阅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) {
}
}
并设置<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
请告知您是否可以改善此问题。
答案 0 :(得分:1)
您的实现看起来不错,并且您始终将
invalid
称为false
,因为您没有添加任何验证。
您可以为asset1
和asset2
添加验证,因此请更改以下行
this.parts = fb.group({'asset1': '', 'asset2': ''});
到
this.parts = this.fb.group({
asset1: ['', Validators.required],
asset2: ['', Validators.required, Validators.minLength(6)]
});
答案 1 :(得分:0)
我所做的是以下内容:
get errorState(): boolean {
return (this.model.invalid && this.model.dirty)
|| (this.ngControl?.invalid && this.ngControl?.dirty);
}
其中 model
是我的本地 FormControl
,ngControl
是父控件。所以当我的控件有错误或父控件无效时,它会返回错误状态。