Angular: updateValueAndValidity from directive
Asked Answered
M

3

6

I have a directive that appends decimals, if the input value is a whole number, on blur. Below is the implementation.

import { Directive, ElementRef, Input, OnInit, HostListener, forwardRef } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';

@Directive({
  selector: '[price]',
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => PriceDirective),
      multi: true
    }
  ]
})
export class PriceDirective implements ControlValueAccessor {

  constructor(private el: ElementRef) { }

  // ControlValueAccessor interface
  private _onChange = (_) => { };
  private _onTouched = () => { };

  @HostListener('blur', ['$event'])
  input(event) {
    !!event.target.value ? $(this.el.nativeElement).val(Number(event.target.value).toFixed(2)) : $(this.el.nativeElement).val(null);

    this._onChange(parseFloat(event.target.value));
    this._onTouched();
  }
  writeValue(value: any): void {
    !!value ? $(this.el.nativeElement).val(Number(value).toFixed(2)) : $(this.el.nativeElement).val(null);
  }

  registerOnChange(fn: (_: any) => void): void { this._onChange = fn; }
  registerOnTouched(fn: any): void { this._onTouched = fn; }

}

Things work as expected.

But, since Angular doesn't trigger validation when the value is changed programatically, the textbox that has this directive, is not validated.

How can I enable validation in this case, by other means than passing the control reference as an input to the directive and calling updateValueAndValidity on it, or calling updateValueAndValidity on input or blur.

It would be great if someone suggests me a way to trigger validation from the directive itself.

Mariehamn answered 27/1, 2018 at 5:23 Comment(1)
Great question, I have the same problem. Upvoted!Schoenfelder
E
2

I solved the same problem in this way. It is my first approach.

  update() {

    // ...

    const el = this.el.nativeElement;
    const reg = new RegExp(this.textMaskConfig.replacement);

    el.value = this.prevStr.replace(reg, this.currentChar);

    // ...

  }

But It doesn't fire the validate event. So I get the NgControl component and used setValue() method.

  constructor(private el: ElementRef, private ctrl: NgControl) {   
  }

  @HostListener('keydown', ['$event']) onKeyDownHandler(e) {
    this.ctrl.control.setValue(value);
  }
Enunciate answered 11/10, 2019 at 14:44 Comment(0)
M
0

I solved what I believe is the same problem. I couldn't get validation to fire when I set the value on the nativeElement like in your example, or with a HostBinding to value like @HostBinding('value') public value: string;. But I could get validation to fire when I set the value through ngModelChange like this:

import { Directive, Input, HostListener, Output, EventEmitter } from '@angular/core';

@Directive({
    selector: '[appPrice]',
})
export class PriceDirective {
    @Output()
    public ngModelChange: EventEmitter<any> = new EventEmitter();

    @HostListener('blur', ['$event.target.value'])
    public formatANumber(value) {
        const cleanedValue = Number(value).toFixed(2);
        this.ngModelChange.emit(cleanedValue);
    }
}
Myriam answered 4/7, 2019 at 19:17 Comment(0)
J
0

it is not clear to me what validation you want to add. As far as I understood, you want to reach input element of form from a directive and manipulate it based on some logic. I ll show you one way with rx.js and based on your validation logic, you can use corresponding operator.

in your generated directive file:

import { Directive, ElementRef } from '@angular/core';
import { NgControl } from '@angular/forms';
import { map } from 'rxjs/operators';

@Directive({
  selector: '[appPrice]',
})

export class PriceDirective {
  // dependency injection for ElementRef has to set in the constructor
  constructor(private el: ElementRef, private controlName: NgControl) {
    console.log(this.el);
    console.log('controlName', controlName);
    // this returns FormControlName (not FormControl) obj. it has name property that tells u which formCpntrol element u r on.
    // FormContolName class binds the FormControl to the "input" element. FormControlName has no direct reference to FormGroup
    // controlName is bound to input element's formGroup's FormGroup
  }
  ngOnInit() {
    console.log(this.controlName.control);
    // this returns the name of the formControl
    console.log(this.controlName.control.parent);
    // this.controlName.control.parent takes us to the FormGroup
    // this.controlName.control.parent returns observable. valueChanges watches all the formControls that you defined. if you have "a","b","c" formControls
    // with pipe() you can add operators to modify the value
    this.controlName.control.parent.valueChanges
      .pipe(map(({ a, b, c }) => // add Some Logic here))
      .subscribe((value) => {
        if (here is True) {
          this.el.nativeElement.classList.add('close');
        } else {
          this.el.nativeElement.classList.remove('close');
        }
      });
  }
}
Jean answered 21/9, 2020 at 0:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.