angular: directive transform value of form control

Viewed 5353

I want to transform slug control when user types in the name control. I am applying transformations like changing spaces to hyphens(-), etc.

I created a directive for this which works partially. It does transform the value for slug input field but it doesn't change the value for slug form control when typing on name.

Here is live code: https://stackblitz.com/edit/angular-slug-transform

Directive:

import { Directive, HostListener } from "@angular/core";
import { NgControl } from "@angular/forms";

@Directive({
  selector: "[formControlName][appSlugTransform]"
})
export class SlugTransformDirective {
  constructor(public ngControl: NgControl) {}

  @HostListener("ngModelChange", ["$event"])
  onModelChange(event) {
    let newVal = this.transform(event);
    this.ngControl.valueAccessor.writeValue(newVal);
  }

  transform(value) {
    let text = value.toLowerCase();
    if (text.charAt(0) == " ") {
      text = text.trim();
    }
    if (text.charAt(text.length - 1) == "-") {
      //text = (text.replace(/-/g, ""));
    }
    text = text.replace(/ +/g, "-");
    text = text.replace(/--/g, "-");
    text = text.normalize("NFKD").replace(/[\u0300-\u036f]/g, ""); // Note: Normalize('NFKD') used to normalize special alphabets like óã to oa
    text = text.replace(/[^a-zA-Z0-9 -]/g, "");

    return text;
  }
}

In the example you can see:

  • If you type in the name input field, slug gets transformed in its input field but its form control value does not transform.
  • When you type in the slug input field then it works correctly i.e both field and control value get transformed.
3 Answers

Finally solved the issue and the magic goes here

ngOnInit(): void {
  this.valueSubscription = this.ngControl.control.valueChanges.subscribe(
    value => {
      const newVal = this.transform(value);
      this.ngControl.control.setValue(newVal, { emitEvent: false });
    }
  );
}

I am using valueChanges instead of HostListener to listen to value change.

And to update value I am now using setValue method of control.

Here is the running code: https://stackblitz.com/edit/angular-slug-directive

The problem is that this.ngControl.valueAccessor.writeValue(newVal) is not triggering the model update. Not exactly sure why that's the case but here's an alternative approach you can use with ElementRef & dispatchEvent to trigger model update:

First update the value of input using ElementRef & then use dispatchEvent.

this.el.value = newVal;
this.el.dispatchEvent(new Event('input'))

demo: https://stackblitz.com/edit/angular-slug-transform-sdmeto



Full updated directive:

import { Directive, HostListener, ElementRef, OnInit } from "@angular/core";
import { NgControl } from "@angular/forms";

@Directive({
  selector: "[formControlName][appSlugTransform]"
})
export class SlugTransformDirective implements OnInit {
  private el: any;

  constructor(public ngControl: NgControl, private elementRef: ElementRef) {
    this.el = this.elementRef.nativeElement;
  }

  ngOnInit() {
    this.el.value = this.transform(this.el.value);
  }

  @HostListener("ngModelChange", [ "$event"])
  onNgModelChange(event) {
    let newVal = this.transform(event);
    // this.ngControl.valueAccessor.writeValue(newVal);
    this.el.value = newVal;
    this.el.dispatchEvent(new Event('input'))
  }

  transform(value) {
    let text = value.toLowerCase();
    if (text.charAt(0) == " ") {
      text = text.trim();
    }
    if (text.charAt(text.length - 1) == "-") {
      //text = (text.replace(/-/g, ""));
    }
    text = text.replace(/ +/g, "-");
    text = text.replace(/--/g, "-");
    text = text.normalize("NFKD").replace(/[\u0300-\u036f]/g, ""); // Note: Normalize('NFKD') used to normalize special alphabets like óã to oa
    text = text.replace(/[^a-zA-Z0-9 -]/g, "");

    return text;
  }
}

Another approach that you can try is to use patchValue instead of writeValue like this:

this.ngControl.control.patchValue(newVal);

That should trigger model change. (not tested)

Related