#angular #angular-reactive-forms #angular-directive
#angular #угловые реактивные формы #angular-директива
Вопрос:
У меня есть директива в Angular, которая реализует ControlValueAccessor. Директива, похоже, работает в стороне от получения значений, установленных в FormControl.setValue(). Как мне получить это обновление значения в директиве?
Вот моя директива
import {
Directive,
ElementRef,
EventEmitter,
forwardRef,
HostListener,
Input,
Output,
Renderer2
} from '@angular/core';
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms';
import {CurrencyPipe} from '@angular/common';
@Directive({
selector: '[currency-input]',
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => CurrencyInputDirective),
multi: true
}
]
})
export class CurrencyInputDirective implements ControlValueAccessor {
// TODO: Allow null value
_currencyString: string = '';
@Input() ciDecimals = 2;
@Input() ciCurrencyCode = 'USD';
@Input() ciMaximumAmount = 10000;
// Pass true if using formControl or formControlName
@Input() ciModelDrivenForm = false;
@Output() ciExceededMax: EventEmitter<any> = new EventEmitter();
onChangeCallback = (_: any) => {};
onTouchedCallback = () => {};
@HostListener('onNgModelChange') onNgModelChange() {
// Never runs
console.log("in ng model change");
}
@HostListener('input', ['$event']) onInput(value: any) {
// Never runs
console.log("in input");
}
@HostListener('blur', []) onBlur() {
this.onTouchedCallback();
}
@HostListener('focus', []) onFocus() {
this.onTouchedCallback();
}
@HostListener('keydown', ['$event']) onKeyDown(e: KeyboardEvent) {
switch (e.key) {
case 'Backspace':
this.handleBackspaceKeyPress(e);
break;
default:
if (isNaN( e.key)) {
e.preventDefault();
} else {
this.handleNumericKeyPress(e);
}
break;
}
}
constructor (
private _renderer: Renderer2,
private _elementRef: ElementRef,
private _currencyPipe: CurrencyPipe
) {}
writeValue(value: any) {
this._renderer.setProperty(this._elementRef.nativeElement, 'value', this.buildElementValue());
this.onChangeCallback(this.buildControlValue());
}
registerOnChange(fn: any) {
this.onChangeCallback = fn;
}
registerOnTouched(fn: any) {
this.onTouchedCallback = fn;
}
setDisabledState(isDisabled: boolean) {
this._renderer.setProperty(this._elementRef.nativeElement, 'disabled', isDisabled);
}
private handleBackspaceKeyPress(e: KeyboardEvent) {
e.preventDefault();
// Remove one digit
if (this._currencyString.length > 0) {
this._currencyString = this._currencyString.slice(0, this._currencyString.length - 1);
}
this.writeValue(this._currencyString);
}
private handleNumericKeyPress(e: KeyboardEvent) {
e.preventDefault();
const newCurrencyString = this._currencyString e.key;
const currencyValue: number = this.convertCurrencyStringToCurrencyValue(newCurrencyString);
if (currencyValue > this.ciMaximumAmount) {
setTimeout(() => {
this.ciExceededMax.emit({ amount: currencyValue, maxAmount: this.ciMaximumAmount });
}, 1);
return;
}
this._currencyString = newCurrencyString;
this.writeValue(this._currencyString);
}
private buildElementValue() {
const currencyDecimals = '1.' this.ciDecimals '-' this.ciDecimals;
const retVal: string = !this._currencyString ? null : this._currencyPipe.transform(
this.convertCurrencyStringToCurrencyValue(this._currencyString),
this.ciCurrencyCode,
'symbol',
currencyDecimals);
return retVal ? retVal : this._currencyPipe.transform(0, this.ciCurrencyCode, 'symbol', currencyDecimals);
}
private buildControlValue() {
return this.convertCurrencyStringToCurrencyValue(this._currencyString);
}
private convertCurrencyValueToCurrencyString(currencyValue: number): string {
return currencyValue.toString().replace(/[^0-9]/g, '');
}
private convertCurrencyStringToCurrencyValue(currencyString: String): number {
const strippedValue: string = currencyString.replace(/[^0-9]/g, '');
if (strippedValue.length === 0) {
return 0;
}
const parsedInt: number = parseInt(strippedValue, 10);
return parsedInt / Math.pow(10, this.ciDecimals);
}
}
Вот мой setValue()
myForm.get('myField').setValue(5.00);
Комментарии:
1.
onChangeCallback
будет вызываться каждый раз, когда изменяется значение. Если вы хотите использовать что-то другое, вы можете использовать инжектор для полученияNgControl
зависимости, которая содержитcontrol
абстрактный элемент управления, имеющийvalueChanges
свойство.2. На самом деле оказывается, что writeValue будет вызываться с новым значением. Я не знаю, почему это никогда не приходило мне в голову.
Ответ №1:
Оказывается, что writeValue вызывался при вызове FormControl.setValue().setValue(). Вот обновленная директива на случай, если она кому-нибудь поможет.
import {
Directive,
ElementRef,
EventEmitter,
forwardRef,
HostListener,
Input,
Output,
Renderer2
} from '@angular/core';
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms';
import {CurrencyPipe} from '@angular/common';
import {isNullOrUndefined} from '../../util';
@Directive({
selector: '[currencyInput]',
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => CurrencyInputDirective),
multi: true
}
]
})
export class CurrencyInputDirective implements ControlValueAccessor {
private _currencyString: string = '';
@Input() ciDecimals = 2;
@Input() ciCurrencyCode = 'USD';
@Input() ciMaximumAmount = 10000;
@Input() ciAllowNull = false;
@Output() ciExceededMax: EventEmitter<any> = new EventEmitter();
onChangeCallback = (value: any) => {
};
onTouchedCallback = () => {
};
@HostListener('blur', []) onBlur() {
this.onTouchedCallback();
}
@HostListener('focus', []) onFocus() {
this.onTouchedCallback();
}
@HostListener('keydown', ['$event']) onKeyDown(e: KeyboardEvent) {
switch (e.key) {
case 'Tab':
// Allow default
break;
case 'Backspace':
this.handleBackspaceKeyPress(e);
break;
default:
if (isNaN( e.key)) {
e.preventDefault();
} else {
this.handleNumericKeyPress(e);
}
break;
}
}
constructor(
private _renderer: Renderer2,
private _elementRef: ElementRef,
private _currencyPipe: CurrencyPipe
) {
}
writeValue(value: any) {
const currencyString: string = this.convertCurrencyValueToCurrencyString(value);
if (currencyString !== this._currencyString) {
const currencyValue: number = this.convertCurrencyStringToCurrencyValue(currencyString);
if (currencyValue > this.ciMaximumAmount) {
setTimeout(() => {
this.ciExceededMax.emit({amount: currencyValue, maxAmount: this.ciMaximumAmount});
}, 1);
return;
}
this._currencyString = currencyString;
}
this._renderer.setProperty(this._elementRef.nativeElement, 'value', this.buildElementValue());
this.onChangeCallback(this.buildControlValue());
}
registerOnChange(fn: any) {
this.onChangeCallback = fn;
}
registerOnTouched(fn: any) {
this.onTouchedCallback = fn;
}
setDisabledState(isDisabled: boolean) {
this._renderer.setProperty(this._elementRef.nativeElement, 'disabled', isDisabled);
}
private handleBackspaceKeyPress(e: KeyboardEvent) {
e.preventDefault();
this.writeValue((this._currencyString.length === 0) ? this._currencyString : this._currencyString.slice(0, this._currencyString.length - 1));
}
private handleNumericKeyPress(e: KeyboardEvent) {
e.preventDefault();
this.writeValue(this._currencyString e.key);
}
private buildElementValue() {
const currencyDecimals = '1.' this.ciDecimals '-' this.ciDecimals;
const controlValue: number = this.buildControlValue();
// Currency format return value
if (!isNullOrUndefined(controlValue)) {
return this._currencyPipe.transform(
controlValue,
this.ciCurrencyCode,
'symbol',
currencyDecimals);
}
return '';
}
private buildControlValue(): number {
return this.convertCurrencyStringToCurrencyValue(this._currencyString);
}
private convertCurrencyValueToCurrencyString(currencyValue: any): string {
if (isNullOrUndefined(currencyValue)) {
return '';
}
if (typeof currencyValue === 'number') {
const currencyDecimals = '1.' this.ciDecimals '-' this.ciDecimals;
currencyValue = this._currencyPipe.transform(currencyValue, this.ciCurrencyCode, 'symbol', currencyDecimals);
}
return currencyValue.toString().replace(/[^0-9]/g, '');
}
private convertCurrencyStringToCurrencyValue(currencyString: String): number {
const strippedValue: string = currencyString.replace(/[^0-9]/g, '');
if (strippedValue.length === 0) {
return (this.ciAllowNull) ? null : 0;
}
const parsedInt: number = parseInt(strippedValue, 10);
return parsedInt / Math.pow(10, this.ciDecimals);
}
}