Пользовательские директивы реактивной формы не запускаются OnInit или когда скрытый ввод отображается *ngIf

Используя реактивные формы в Angular, я создал две директивы: одну, которая преобразует значения из центов в доллары путем деления на 100, а другую, которая преобразует доллары в центы путем умножения на 100. Это работает, когда я вручную устанавливаю элементы управления формой, но не получается. применил OnInit, и он не применяется, когда ввод формы отображается, когда он скрыт за *ngIf.

Я создал stackblitz с примером. В OnInit должно отображаться $12,345.50, но вместо этого отображается $12,34550.00 и одно и то же значение, когда отображается поле формы, пока они не будут установлены вручную с помощью кнопок. Пример приведен в Angular v15, но я использую v18, если это помогает с решением, но могу воспроизвести его аналогичным образом в любом из них.

Есть ли способ запустить директивы OnInit и при отображении ввода? Или мне следует реализовать это по-другому?

🤔 А знаете ли вы, что...
Angular Universal позволяет рендерить веб-приложение на сервере для улучшения SEO и производительности.


2
70
1

Ответ:

Решено

Мы можем получить доступ к директиве NgControl и напрямую исправить преобразованное значение при первой загрузке.

Также не используйте *ngIf при работе со скрытыми формами. Из-за этого элементы управления не работают должным образом, поскольку они удалены из DOM; вместо этого всегда используйте атрибут [hidden]:

import { AfterContentInit, Directive, Input, inject } from '@angular/core';
import {
  DefaultValueAccessor,
  FormControlName,
  NgControl,
} from '@angular/forms';
import {
  maskitoNumberOptionsGenerator,
  maskitoParseNumber,
} from '@maskito/kit';

export const dollarMask = maskitoNumberOptionsGenerator({
  min: 0,
  // Maximum to avoid floating point precision errors
  // which will occur when numeric values are too large
  // exceeding Number.MAX_SAFE_INTEGER
  max: 1000000000,
  precision: 2,
  decimalSeparator: '.',
  decimalZeroPadding: true,
  thousandSeparator: ',',
  prefix: '$',
});

@Directive({
  standalone: true,
  selector: '[maskito][toDollars]',
})
export class ToDollarsDirective {
  private readonly accessor = inject(DefaultValueAccessor, { self: true });
  private readonly formControlName = inject(NgControl, { self: true });

  ngOnInit() {
    const initialValue = this.formControlName.value;
    const dollars1 = initialValue != null ? initialValue / 100 : initialValue;
    this.formControlName.control!.patchValue(dollars1);
  }

  ngOnAfterContentInit() {
    const original = this.accessor.writeValue.bind(this.accessor);
    this.accessor.writeValue = (value: any) => {
      const dollars = value != null ? value / 100 : value;
      original(dollars);
    };
  }
}

@Directive({
  standalone: true,
  selector: '[maskito][toCents]',
})
export class ToCentsDirective {
  private readonly accessor = inject(DefaultValueAccessor, { self: true });
  private readonly formControlName = inject(NgControl, { self: true });


  ngAfterContentInit() {
    const original = this.accessor.onChange.bind(this.accessor);
    this.accessor.onChange = (value: any) => {
      const cents = maskitoParseNumber(value) * 100;
      original(cents);
    };
  }
}

Демо-версия Stackblitz