Интеграция библиотеки AutoNumeric в Angular требует понимания особенностей жизненного цикла компонентов, механизма привязки данных, работы с формами и взаимодействия с DOM. Поскольку AutoNumeric напрямую управляет HTML-элементом, Angular и библиотека могут конфликтовать при обновлении значения поля. Для корректной интеграции требуется правильно организовать двустороннюю синхронизацию.
Установка через npm:
npm install autonumeric
Импорт в компоненте:
import AutoNumeric from 'autonumeric';
<input #priceInput type="text">
import {
AfterViewInit,
Component,
ElementRef,
ViewChild
} from '@angular/core';
import AutoNumeric from 'autonumeric';
@Component({
selector: 'app-root',
templateUrl: './app.component.html'
})
export class AppComponent implements AfterViewInit {
@ViewChild('priceInput')
priceInput!: ElementRef;
autoNumeric!: AutoNumeric;
ngAfterViewInit(): void {
this.autoNumeric = new AutoNumeric(
this.priceInput.nativeElement,
{
digitGroupSeparator: ' ',
decimalCharacter: ',',
decimalPlaces: 2,
currencySymbol: '₸ '
}
);
}
}
ngAfterViewInitЭкземпляр AutoNumeric требует существования реального DOM-элемента.
На этапе constructor или ngOnInit ссылка
ViewChild ещё недоступна.
Правильный жизненный цикл:
ngAfterViewInit()
Именно в этот момент Angular завершает рендеринг шаблона.
const formatted = this.autoNumeric.getFormatted();
Результат:
₸ 12 500,00
const numeric = this.autoNumeric.getNumber();
Результат:
12500
const raw = this.autoNumeric.getNumericString();
Результат:
12500.00
this.autoNumeric.set(45000);
this.autoNumeric.set('12500.75');
this.autoNumeric.clear();
Наиболее важная часть интеграции — совместимость с Angular Forms.
Существует два основных подхода:
ngModel<input
#amountInput
type="text"
[(ngModel)]="amount">
AutoNumeric изменяет значение DOM напрямую, а Angular ожидает
контроль над полем через ngModel.
В результате возможны:
import {
AfterViewInit,
Component,
ElementRef,
ViewChild
} from '@angular/core';
import AutoNumeric from 'autonumeric';
@Component({
selector: 'app-root',
templateUrl: './app.component.html'
})
export class AppComponent implements AfterViewInit {
@ViewChild('amountInput')
amountInput!: ElementRef;
amount = 0;
anElement!: AutoNumeric;
ngAfterViewInit(): void {
this.anElement = new AutoNumeric(
this.amountInput.nativeElement,
{
decimalPlaces: 2
}
);
this.amountInput.nativeElement.addEventListener(
'autoNumeric:rawValueModified',
() => {
this.amount = this.anElement.getNumber();
}
);
}
}
Reactive Forms значительно лучше подходят для интеграции с AutoNumeric.
import {
AfterViewInit,
Component,
ElementRef,
ViewChild
} from '@angular/core';
import {
FormControl,
FormGroup
} from '@angular/forms';
import AutoNumeric from 'autonumeric';
@Component({
selector: 'app-root',
templateUrl: './app.component.html'
})
export class AppComponent implements AfterViewInit {
@ViewChild('salaryInput')
salaryInput!: ElementRef;
form = new FormGroup({
salary: new FormControl(0)
});
an!: AutoNumeric;
ngAfterViewInit(): void {
this.an = new AutoNumeric(
this.salaryInput.nativeElement,
{
currencySymbol: '$ '
}
);
this.salaryInput.nativeElement.addEventListener(
'autoNumeric:rawValueModified',
() => {
const value = this.an.getNumber();
this.form.patchValue(
{
salary: value
},
{
emitEvent: false
}
);
}
);
}
}
emitEvent: falseБез этого параметра возможно появление бесконечного цикла:
Параметр предотвращает повторную генерацию событий.
Наиболее правильный способ интеграции AutoNumeric — собственная директива.
Такой подход:
import {
Directive,
ElementRef,
AfterViewInit
} from '@angular/core';
import AutoNumeric from 'autonumeric';
@Directive({
selector: '[appAutoNumeric]'
})
export class AutoNumericDirective implements AfterViewInit {
private anElement!: AutoNumeric;
constructor(private el: ElementRef) {}
ngAfterViewInit(): void {
this.anElement = new AutoNumeric(
this.el.nativeElement,
{
digitGroupSeparator: ' ',
decimalCharacter: ','
}
);
}
}
<input type="text" appAutoNumeric>
Для полной совместимости с Angular Forms требуется реализация
интерфейса ControlValueAccessor.
import {
Directive,
ElementRef,
forwardRef,
AfterViewInit
} from '@angular/core';
import {
ControlValueAccessor,
NG_VALUE_ACCESSOR
} from '@angular/forms';
import AutoNumeric from 'autonumeric';
@Directive({
selector: '[appAutoNumeric]',
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => AutoNumericDirective),
multi: true
}
]
})
export class AutoNumericDirective
implements ControlValueAccessor, AfterViewInit {
private autoNumeric!: AutoNumeric;
private onChange: any = () => {};
private onTouched: any = () => {};
constructor(private el: ElementRef) {}
ngAfterViewInit(): void {
this.autoNumeric = new AutoNumeric(
this.el.nativeElement,
{
currencySymbol: '$ ',
decimalPlaces: 2
}
);
this.el.nativeElement.addEventListener(
'autoNumeric:rawValueModified',
() => {
this.onChange(
this.autoNumeric.getNumber()
);
}
);
this.el.nativeElement.addEventListener(
'blur',
() => {
this.onTouched();
}
);
}
writeValue(value: any): void {
if (value !== null && value !== undefined) {
this.autoNumeric.set(value);
}
}
registerOnChange(fn: any): void {
this.onCha nge = fn;
}
registerOnTouched(fn: any): void {
this.onTouc hed = fn;
}
setDisabledState(isDisabled: boolean): void {
this.el.nativeElement.disabled = isDisabled;
}
}
<input
type="text"
formControlName="price"
appAutoNumeric>
Жёстко заданные настройки неудобны. Директива должна поддерживать конфигурацию.
import {
Directive,
ElementRef,
Input,
AfterViewInit
} from '@angular/core';
import AutoNumeric from 'autonumeric';
@Directive({
selector: '[appAutoNumeric]'
})
export class AutoNumericDirective
implements AfterViewInit {
@Input()
options: any = {};
an!: AutoNumeric;
constructor(private el: ElementRef) {}
ngAfterViewInit(): void {
this.an = new AutoNumeric(
this.el.nativeElement,
this.options
);
}
}
<input
type="text"
appAutoNumeric
[options]="{
currencySymbol: '€ ',
decimalPlaces: 2
}">
Angular может изменять входные параметры после инициализации.
OnChangesimport {
Directive,
ElementRef,
Input,
OnChanges,
SimpleChanges
} from '@angular/core';
import AutoNumeric from 'autonumeric';
@Directive({
selector: '[appAutoNumeric]'
})
export class AutoNumericDirective
implements OnChanges {
@Input()
options: any = {};
an!: AutoNumeric;
constructor(private el: ElementRef) {
this.an = new AutoNumeric(
this.el.nativeElement,
this.options
);
}
ngOnChanges(changes: SimpleChanges): void {
if (changes['options']) {
this.an.update(
changes['options'].currentValue
);
}
}
}
AutoNumeric генерирует собственные DOM-события.
| Событие | Описание |
|---|---|
autoNumeric:initialized |
Инициализация |
autoNumeric:rawValueModified |
Изменение числового значения |
autoNumeric:formatted |
Форматирование |
autoNumeric:minExceeded |
Значение меньше минимума |
autoNumeric:maxExceeded |
Значение больше максимума |
this.el.nativeElement.addEventListener(
'autoNumeric:maxExceeded',
(event: Event) => {
console.log('Максимум превышен');
}
);
Angular уничтожает компоненты при смене маршрутов и условий
*ngIf.
Если не удалить AutoNumeric вручную, возможны:
OnDestroyimport {
Directive,
OnDestroy
} from '@angular/core';
export class AutoNumericDirective
implements OnDestroy {
an!: AutoNumeric;
ngOnDestroy(): void {
if (this.an) {
this.an.remove();
}
}
}
AutoNumeric совместим с Angular Material, однако имеются особенности.
<mat-form-field appearance="outline">
<mat-label>Сумма</mat-label>
<input
matInput
appAutoNumeric
[options]="{
currencySymbol: '₽ ',
decimalPlaces: 2
}">
</mat-form-field>
AutoNumeric может изменять содержимое поля, что влияет на анимацию
mat-label.
Решение:
emptyInputBehavior: 'null'
Material ожидает null, а AutoNumeric может возвращать
пустую строку.
Рекомендуемая настройка:
emptyInputBehavior: null
При динамическом создании строк экземпляры AutoNumeric необходимо создавать отдельно для каждого input.
*ngFor<tr *ngFor="let item of items">
<td>
<input
type="text"
appAutoNumeric>
</td>
</tr>
AutoNumeric изменяет DOM напрямую, обходя Angular.
Иногда Angular не замечает изменения.
import {
ChangeDetectorRef
} from '@angular/core';
constructor(
private cdr: ChangeDetectorRef
) {}
this.cdr.detectChanges();
При стратегии ChangeDetectionStrategy.OnPush Angular
реагирует только на:
AutoNumeric работает вне Angular Zone.
import {
NgZone
} from '@angular/core';
constructor(
private zone: NgZone
) {}
this.zone.run(() => {
this.control.setValue(
this.an.getNumber()
);
});
AutoNumeric использует window и DOM API.
На сервере Angular Universal DOM отсутствует.
import {
isPlatformBrowser
} from '@angular/common';
import {
Inject,
PLATFORM_ID
} from '@angular/core';
constructor(
@Inject(PLATFORM_ID)
private platformId: object
) {}
if (isPlatformBrowser(this.platformId)) {
this.an = new AutoNumeric(
this.el.nativeElement
);
}
При ленивой загрузке модулей директиву необходимо экспортировать.
@NgModule({
declarations: [
AutoNumericDirective
],
exports: [
AutoNumericDirective
]
})
export class SharedModule {}
AutoNumeric предоставляет типы для TypeScript.
import AutoNumeric, {
Options
} from 'autonumeric';
options: Partial<Options> = {
currencySymbol: '$ ',
decimalPlaces: 2
};
Частая ситуация — получение данных с сервера после инициализации.
this.http.get('/api/product')
.subscribe((data: any) => {
this.an.set(data.price);
});
Распространённая ошибка:
this.control.setValue(5000);
Angular обновляет модель, но AutoNumeric не знает об изменении.
this.an.set(5000);
или внутри writeValue.
{
currencySymbol: '₸ ',
digitGroupSeparator: ' ',
decimalCharacter: ','
}
{
currencySymbol: '$ ',
decimalCharacter: '.',
digitGroupSeparator: ','
}
{
currencySymbol: '€ ',
decimalCharacter: ',',
digitGroupSeparator: '.'
}
{
suffixText: ' %',
decimalPlaces: 2
}
{
minimumValue: '0',
maximumValue: '1000000'
}
{
minimumValue: '0'
}
if (this.an) {
this.an.set(100);
}
if (this.el?.nativeElement) {
this.an = new AutoNumeric(
this.el.nativeElement
);
}
При большом количестве полей рекомендуется:
OnPush;detectChanges.Наиболее стабильная схема интеграции:
@Input.Такая архитектура обеспечивает: