提问人:Andrea Moretti 提问时间:11/9/2023 更新时间:11/9/2023 访问量:42
Angular ReactiveForms:ControlValueAccessor 在不通知更改的情况下验证初始值
Angular ReactiveForms: ControlValueAccessor validate initial value without notifying change
问:
我试图找到一些解决方法或解决方案,但没有结果。 我正在尝试实现 CustomValueAccessor,它可以验证给定控件的初始值,如果没有,则在不通知对父控件的更改的情况下更改它,而是更改其值。
问题出在 ngOnInit 循环中。如果我调用此代码:
if (!this._initialIsValid) {
this.onChange(this.period);
this.ngControl.control?.markAsPristine();
}
控件的值在内部和父级中都发生了更改,但我在父级上收到通知。
如果我不调用 ,则该值仅在控件内部更改,因此我不会收到通知,但父级仍具有初始值。
我不需要通知更改,因为在父组件中,我订阅并触发了一些 http 请求。formGroup
onChange
formGroup
formGroup.valueChanges()
我还尝试过:
if (!this._initialIsValid) {
this.ngControl.control?.setValue(this.period, {emitEvent: false});
this.ngControl.control?.markAsPristine();
}
但它的工作方式与并且仍然有通知。onChange
基本上,我正在实现月年选择器(使用 Angular Material),以便能够在将更改发送到父控件之前选择月年并将其转换为 yyyyMM 格式。我有 dateFilter 函数,允许 datepicker 禁用不可用的日期。 验证函数基本上检查数字数组是否包含 yyyyMM 给定的周期。如果数组为空,则每个日期都被视为有效。
基本句点选取器-html
<mat-form-field [ngClass]="fullwidth ? 'w100' : ''">
<mat-label>{{ label }}</mat-label>
<input
[placeholder]="label"
matInput
[matDatepicker]="dp"
readonly
[required]="required"
[value]="inputDate"
[disabled]="disabled"
[matDatepickerFilter]="dateFilter"
/>
<!-- <mat-hint>MM/YYYY</mat-hint> -->
<button
mat-icon-button
matIconPrefix
*ngIf="allowClear && period > 0"
(click)="clearPeriod(dp)"
>
<mat-icon>cancel</mat-icon>
</button>
<mat-datepicker-toggle matIconSuffix [for]="dp"></mat-datepicker-toggle>
<mat-datepicker
#dp
disabled="false"
startView="multi-year"
(monthSelected)="setMonthAndYear($event, dp)"
panelClass="month-picker"
>
</mat-datepicker>
<mat-error *ngIf="required">Campo obbligatorio</mat-error>
</mat-form-field>
basic-period-picker.component.ts
import { Component, Input, Optional, Self, ViewEncapsulation } from '@angular/core';
import { ControlValueAccessor, NgControl } from '@angular/forms';
import { MAT_DATE_FORMATS } from '@angular/material/core';
import { MatDatepicker } from '@angular/material/datepicker';
import { OWTAM_PERIOD_FORMATS } from '../period-date-format';
import { PeriodHelper } from '../period-helper';
import { DateTime } from 'luxon';
import * as _ from 'lodash';
@Component({
selector: 'owtam-basic-period-picker',
templateUrl: './basic-period-picker.component.html',
styleUrls: ['./basic-period-picker.component.scss'],
providers: [
// { provide: NG_VALUE_ACCESSOR, useExisting: BasicPeriodPickerComponent, multi: true },
{ provide: MAT_DATE_FORMATS, useValue: OWTAM_PERIOD_FORMATS }
],
encapsulation: ViewEncapsulation.None,
})
export class BasicPeriodPickerComponent implements ControlValueAccessor {
@Input()
label: string = "Periodo";
@Input()
allowClear: boolean = true;
@Input()
required: boolean = false;
@Input()
disabled: boolean = false;
@Input()
period!: number;
@Input()
openCalendarOnClear: boolean = true;
@Input()
fullwidth: boolean = true;
@Input()
validPeriods: number[] = [];
inputDate!: DateTime | undefined;
ngControl!: NgControl;
constructor(
@Optional() @Self() ngControl: NgControl
) {
if (ngControl) {
this.ngControl = ngControl;
this.ngControl.valueAccessor = this;
}
}
private _initialValidation: boolean = true;
private _initialIsValid: boolean = false;
onChange!: (date: number) => void;
onTouched!: () => void;
writeValue(period: number): void {
const value = this._getValidValue(period);
if (this._initialValidation) {
this._initialIsValid = _.isEqual(period, value);
}
this.period = value;
this.inputDate = PeriodHelper.periodToDate(this.period);
}
registerOnChange(fn: any): void {
this.onChange = fn;
}
registerOnTouched(fn: any): void {
this.onTouched = fn;
}
setDisabledState?(isDisabled: boolean): void {
this.disabled = isDisabled;
}
ngOnInit() {
// if (this.ngControl.control) {
// this.ngControl.control.setValue(this.period);
// // { onlySelf: false, emitModelToViewChange: false, emitViewToModelChange: false, emitEvent: false }
// }
console.log('initialIsValid: ', this._initialIsValid);
if (!this._initialIsValid) {
this.onChange(this.period);
this.ngControl.control?.markAsPristine();
}
this._initialValidation = false;
}
dateFilter = (d: DateTime | null): boolean => {
return PeriodHelper.periodsFilter(this.validPeriods, d);
}
// Evento scatenato alla fine della selezione (anno e mese)
// Viene utilizzata la classe globale OWGlobal per trasformare le date in IDPeriodo e viceversa
setMonthAndYear(date: DateTime, datepicker: MatDatepicker<DateTime>) {
this.inputDate = date;
this.period = PeriodHelper.dateToPeriod(date);
this.onChange(this.period);
datepicker.close();
}
clearPeriod(dp: MatDatepicker<DateTime>) {
this.period = 0;
this.inputDate = undefined;
this.onChange(0);
if (this.openCalendarOnClear) {
dp.open();
}
}
private _getValidValue(period: number) {
if (period == 0) {
period = PeriodHelper.currentPeriod();
}
if (this.validPeriods && this.validPeriods.length > 0) {
if (this.validPeriods.length === 1) {
period = this.validPeriods[0];
} else {
const isValid = this.validPeriods.includes(period);
if (!isValid) {
const closest = this.validPeriods
.map(p => { return { diff: Math.abs(period - p), value: p } })
.sort((a, b) => a.diff - b.diff)[0].value;
period = closest;
}
}
}
return period;
}
}
period-helper.ts
import { DateTime } from "luxon";
export class PeriodHelper {
public static dateToPeriod(date: DateTime) {
if (date) {
const year = date.year;
const month = date.month; // Mese inizia da 0, aggiungiamo 1 per ottenere il mese corretto
const yearMonth = year * 100 + month; // Componiamo il numero yyyyMM
return yearMonth;
}
return 0;
}
// Da periodo [YYYYMM] a data
public static periodToDate(period: number): DateTime | undefined {
if (period) {
const year = Math.floor(period / 100); // Ottieni l'anno dividendo per 100 e arrotondando verso il basso
const month = period % 100; // Ottieni il mese prendendo il resto della divisione per 100
const date = DateTime.fromObject({year, month, day: 1}); // Sottrai 1 al mese perché inizia da 0, quindi il primo mese è 0 (Gennaio)
return date;
}
return undefined;
}
public static currentPeriod() {
return this.dateToPeriod(DateTime.now());
}
public static periodsFilter(validPeriods: number[], d: DateTime | null): boolean {
if (d) {
if (validPeriods && validPeriods.length > 0) {
const p = PeriodHelper.dateToPeriod(d);
return validPeriods.includes(p);
}
}
return true;
}
}
component.ts
validPeriods = [201804, 202205, 202206, 202207, 202401, 202501];
filterForm = new FormGroup({
//you can try even with PeriodHelper.currentPeriod() as initial value
period: new FormControl<number>(0, {nonNullable: true}),
})
组件:.html
<div [formGroup]="filterForm">
<owtam-basic-period-picker
[validPeriods]="validPeriods"
formControlName="period"
[allowClear]="false"
>
</div>
我想在组件中进行此验证,因为我将在我的应用程序中重用很多,因此我无法为每个父级进行验证。
对不起,我的英语不好
提前致谢
答: 暂无答案
评论