Bur*_*nee 21 javascript angular-material angular-validation angular controlvalueaccessor
为了展示一种真实世界的例子,假设我们想在我们的应用程序中使用@ angular/material的datepicker.
我们希望在很多页面上使用它,因此我们希望将它添加到具有相同配置的表单中非常容易.为了满足这一需求,我们在<mat-datepicker>ControlValueAccessor实现周围创建了一个自定义角度组件,以便能够[(ngModel)]在其上使用.
我们希望处理组件中的典型验证,但同时,我们希望为包含我们的外部组件提供验证结果CustomDatepickerComponent.
作为一个简单的解决方案,我们可以实现这样的validate()方法(innerNgModel来自导出的ngModel:.#innerNgModel="ngModel"请参阅本问题末尾的完整代码):
validate() {
return (this.innerNgModel && this.innerNgModel.errors) || null;
}
Run Code Online (Sandbox Code Playgroud)
此时,我们可以以非常简单的方式在任何表单组件中使用datepicker(如我们所愿):
<custom-datepicker [(ngModel)]="myDate"></custom-datepicker>
我们还可以扩展上面的行以获得更好的调试体验(像这样):
<custom-datepicker [(ngModel)]="myDate" #date="ngModel"></custom-datepicker>
<pre>{{ date.errrors | json }}</pre>
Run Code Online (Sandbox Code Playgroud)
只要我更改自定义datepicker组件中的值,一切正常.如果datepicker有任何错误,则周围的表单仍然无效(如果datepicker有效,它将变为有效).
但!
如果myDate外部组件的成员(一个作为ngModel传递)由外部组件(如:)更改this.myDate= null,则会发生以下情况:
writeValue()该CustomDatepickerComponent的运行,并将其更新日期选择器的值.validate()该CustomDatepickerComponent的运行,但在这一点上innerNgModel没有更新,因此返回以前的状态的验证.要解决此问题,我们可以从setTimeout中的组件发出更改:
public writeValue(data) {
this.modelValue = data ? moment(data) : null;
setTimeout(() => { this.emitChange(); }, 0);
}
Run Code Online (Sandbox Code Playgroud)
在这种情况下,emitChange(自定义组件的广播更改)将触发新的验证.并且由于setTimeout,它将在下一个周期运行,而innerNgModel已经更新.
我的问题是,如果有更好的方法来处理这个问题而不是使用setTimeout? 如果可能的话,我会坚持模板驱动的实现.
提前致谢!
该示例的完整源代码:
定制datepicker.component.ts
import {Component, forwardRef, Input, ViewChild} from '@angular/core';
import {ControlValueAccessor, NG_VALIDATORS, NG_VALUE_ACCESSOR, NgModel} from '@angular/forms';
import * as moment from 'moment';
import {MatDatepicker, MatDatepickerInput, MatFormField} from '@angular/material';
import {Moment} from 'moment';
const AC_VA: any = {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => CustomDatepickerComponent),
multi: true
};
const VALIDATORS: any = {
provide: NG_VALIDATORS,
useExisting: forwardRef(() => CustomDatepickerComponent),
multi: true,
};
const noop = (_: any) => {};
@Component({
selector: 'custom-datepicker',
templateUrl: './custom-datepicker.compnent.html',
providers: [AC_VA, VALIDATORS]
})
export class CustomDatepickerComponent implements ControlValueAccessor {
constructor() {}
@Input() required: boolean = false;
@Input() disabled: boolean = false;
@Input() min: Date = null;
@Input() max: Date = null;
@Input() label: string = null;
@Input() placeholder: string = 'Pick a date';
@ViewChild('innerNgModel') innerNgModel: NgModel;
private propagateChange = noop;
public modelChange(event) {
this.emitChange();
}
public writeValue(data) {
this.modelValue = data ? moment(data) : null;
setTimeout(() => { this.emitChange(); }, 0);
}
public emitChange() {
this.propagateChange(!this.modelValue ? null : this.modelValue.toDate());
}
public registerOnChange(fn: any) { this.propagateChange = fn; }
public registerOnTouched() {}
validate() {
return (this.innerNgModel && this.innerNgModel.errors) || null;
}
}
Run Code Online (Sandbox Code Playgroud)
和模板(custom-datepicker.compnent.html):
<mat-form-field>
<mat-label *ngIf="label">{{ label }}</mat-label>
<input matInput
#innerNgModel="ngModel"
[matDatepicker]="#picker"
[(ngModel)]="modelValue"
(ngModelChange)="modelChange($event)"
[disabled]="disabled"
[required]="required"
[placeholder]="placeholder"
[min]="min"
[max]="max">
<mat-datepicker-toggle matSuffix [for]="picker"></mat-datepicker-toggle>
<mat-datepicker #picker></mat-datepicker>
<mat-error *ngIf="innerNgModel?.errors?.required">This field is required!</mat-error>
<mat-error *ngIf="innerNgModel?.errors?.matDatepickerMin">Date is too early!</mat-error>
<mat-error *ngIf="innerNgModel?.errors?.matDatepickerMax">Date is too late!</mat-error>
</mat-form-field>
Run Code Online (Sandbox Code Playgroud)
周围的微模块(custom-datepicker.module.ts):
import {NgModule} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {MatDatepickerModule, MatFormFieldModule, MatInputModule, MAT_DATE_LOCALE, MAT_DATE_FORMATS} from '@angular/material';
import {CustomDatepickerComponent} from './custom-datepicker.component';
import {MAT_MOMENT_DATE_ADAPTER_OPTIONS, MatMomentDateModule} from '@angular/material-moment-adapter';
import {CommonModule} from '@angular/common';
const DATE_FORMATS = {
parse: {dateInput: 'YYYY MM DD'},
display: {dateInput: 'YYYY.MM.DD', monthYearLabel: 'MMM YYYY', dateA11yLabel: 'LL', monthYearA11yLabel: 'MMMM YYYY'}
};
@NgModule({
imports: [
CommonModule,
FormsModule,
MatMomentDateModule,
MatFormFieldModule,
MatInputModule,
MatDatepickerModule
],
declarations: [
CustomDatepickerComponent
],
exports: [
CustomDatepickerComponent
],
providers: [
{provide: MAT_DATE_LOCALE, useValue: 'es-ES'},
{provide: MAT_DATE_FORMATS, useValue: DATE_FORMATS},
{provide: MAT_MOMENT_DATE_ADAPTER_OPTIONS, useValue: {useUtc: false}}
]
})
export class CustomDatepickerModule {}
Run Code Online (Sandbox Code Playgroud)
和外部组件的一部分:
<form #outerForm="ngForm" (ngSubmit)="submitForm(outerForm)">
...
<custom-datepicker [(ngModel)]="myDate" #date="ngModel"></custom-datepicker>
<pre>{{ date.errors | json }}</pre>
<button (click)="myDate = null">set2null</button>
...
Run Code Online (Sandbox Code Playgroud)
我面临着同样的任务,并且在处理本地模型的绑定和更改时采取了不同的方法。
我没有分离并手动设置ngModelChange回调,而是将本地变量隐藏在一对 getter\setter 后面,在其中调用我的回调。
在您的情况下,代码将如下所示:
在custom-datepicker.component.html:
<input matInput
#innerNgModel="ngModel"
[matDatepicker]="#picker"
[(ngModel)]="modelValue"
[disabled]="disabled"
[required]="required"
[placeholder]="placeholder"
[min]="min"
[max]="max">
Run Code Online (Sandbox Code Playgroud)
而在custom-datepicker.component.ts:
get modelValue(){
return this._modelValue;
}
set modelValue(newValue){
if(this._modelValue != newValue){
this._modelValue = newValue;
this.emitChange();
}
}
public writeValue(data) {
this.modelValue = data ? moment(data) : null;
}
Run Code Online (Sandbox Code Playgroud)
您可以在https://github.com/cdigruttola/GestioneTessere/tree/master/Server/frontend/src/app/viewedit中查看实际组件
我不知道这是否会产生影响,但在测试应用程序时,我在验证处理中没有发现任何问题,并且实际用户也没有向我报告任何问题。
| 归档时间: |
|
| 查看次数: |
902 次 |
| 最近记录: |