File

src/app/core/common-components/basic-autocomplete/custom-form-control.directive.ts

Description

Extend this base class to implement custom input controls to be used as form fields.

also refer to available public resources on Custom Form Controls:

Implements

ControlValueAccessor MatFormFieldControl OnDestroy DoCheck

Index

Properties
Methods
Inputs
Outputs
HostBindings
Accessors

Constructor

constructor(elementRef: ElementRef, errorStateMatcher: ErrorStateMatcher, ngControl: NgControl, parentForm: NgForm, parentFormGroup: FormGroupDirective)
Parameters :
Name Type Optional
elementRef ElementRef<HTMLElement> No
errorStateMatcher ErrorStateMatcher No
ngControl NgControl No
parentForm NgForm No
parentFormGroup FormGroupDirective No

Inputs

aria-describedby
Type : string
disabled
Type : boolean
placeholder
Type : string
required
Type : boolean
value
Type : T

Outputs

valueChange
Type : EventEmitter

HostBindings

id
Type : string
Default value : `custom-form-control-${CustomFormControlDirective.nextId++}`

Methods

blur
blur()
Returns : void
focus
focus()
Returns : void
onContainerClick
onContainerClick(event: MouseEvent)
Parameters :
Name Type Optional
event MouseEvent No
Returns : void
registerOnChange
registerOnChange(fn: any)
Parameters :
Name Type Optional
fn any No
Returns : void
registerOnTouched
registerOnTouched(fn: any)
Parameters :
Name Type Optional
fn any No
Returns : void
setDescribedByIds
setDescribedByIds(ids: string[])
Parameters :
Name Type Optional
ids string[] No
Returns : void
setDisabledState
setDisabledState(isDisabled: boolean)
Parameters :
Name Type Optional
isDisabled boolean No
Returns : void
writeValue
writeValue(val: T)
Parameters :
Name Type Optional
val T No
Returns : void

Properties

_disabled
Default value : false
_value
Type : T
controlType
Type : string
Default value : "custom-control"
Public elementRef
Type : ElementRef<HTMLElement>
errorState
Default value : false
Public errorStateMatcher
Type : ErrorStateMatcher
focused
Default value : false
id
Default value : `custom-form-control-${CustomFormControlDirective.nextId++}`
Decorators :
@HostBinding()
Static nextId
Type : number
Default value : 0
Public ngControl
Type : NgControl
Decorators :
@Optional()
@Self()
onChange
Default value : () => {...}
onTouched
Default value : () => {...}
Public parentForm
Type : NgForm
Decorators :
@Optional()
Public parentFormGroup
Type : FormGroupDirective
Decorators :
@Optional()
stateChanges
Default value : new Subject<void>()
touched
Default value : false

Accessors

required
getrequired()
setrequired(req: boolean)
Parameters :
Name Type Optional
req boolean No
Returns : void
empty
getempty()
shouldLabelFloat
getshouldLabelFloat()
disabled
getdisabled()
setdisabled(value: boolean)
Parameters :
Name Type Optional
value boolean No
Returns : void
value
getvalue()
setvalue(value: T)
Parameters :
Name Type Optional
value T No
Returns : void
import {
  AbstractControl,
  ControlValueAccessor,
  FormGroupDirective,
  NgControl,
  NgForm,
  Validators,
} from "@angular/forms";
import { MatFormFieldControl } from "@angular/material/form-field";
import {
  Directive,
  DoCheck,
  ElementRef,
  EventEmitter,
  HostBinding,
  Input,
  OnDestroy,
  Optional,
  Output,
  Self,
} from "@angular/core";
import { Subject } from "rxjs";
import { coerceBooleanProperty } from "@angular/cdk/coercion";
import { ErrorStateMatcher } from "@angular/material/core";

/**
 * Extend this base class to implement custom input controls to be used as form fields.
 *
 * also refer to available public resources on Custom Form Controls:
 * - https://material.angular.io/guide/creating-a-custom-form-field-control
 * - https://www.youtube.com/watch?v=CD_t3m2WMM8
 */
@Directive()
export abstract class CustomFormControlDirective<T>
  implements ControlValueAccessor, MatFormFieldControl<T>, OnDestroy, DoCheck
{
  static nextId = 0;
  @HostBinding()
  id = `custom-form-control-${CustomFormControlDirective.nextId++}`;
  // eslint-disable-next-line @angular-eslint/no-input-rename
  @Input("aria-describedby") userAriaDescribedBy: string;
  @Input() placeholder: string;

  @Input()
  get required() {
    return this._required;
  }

  set required(req: boolean) {
    this._required = coerceBooleanProperty(req);
    this.stateChanges.next();
  }

  private _required = false;

  stateChanges = new Subject<void>();
  focused = false;
  touched = false;
  errorState = false;
  controlType = "custom-control";
  onChange = (_: any) => {};
  onTouched = () => {};

  get empty() {
    return !this.value;
  }

  get shouldLabelFloat() {
    return this.focused || !this.empty;
  }

  @Input()
  get disabled(): boolean {
    return this._disabled;
  }

  set disabled(value: boolean) {
    this._disabled = coerceBooleanProperty(value);
    this.stateChanges.next();
  }

  _disabled = false;

  @Input() get value(): T {
    return this._value;
  }

  set value(value: T) {
    if (value === this._value) return;

    this._value = value;
    this.onChange(value);
    this.valueChange.emit(value);
    this.stateChanges.next();
  }

  _value: T;

  @Output() valueChange = new EventEmitter<T>();

  constructor(
    public elementRef: ElementRef<HTMLElement>,
    public errorStateMatcher: ErrorStateMatcher,
    @Optional() @Self() public ngControl: NgControl,
    @Optional() public parentForm: NgForm,
    @Optional() public parentFormGroup: FormGroupDirective,
  ) {
    if (this.ngControl != null) {
      this.ngControl.valueAccessor = this;
    }

    this.elementRef.nativeElement.addEventListener("focusin", () =>
      this.focus(),
    );
    this.elementRef.nativeElement.addEventListener("focusout", () =>
      this.blur(),
    );
  }

  ngOnDestroy() {
    this.stateChanges.complete();
  }

  focus() {
    this.focused = true;
    this.stateChanges.next();
  }

  blur() {
    this.touched = true;
    this.focused = false;
    this.onTouched();
    this.stateChanges.next();
  }

  setDescribedByIds(ids: string[]) {
    this.elementRef.nativeElement.setAttribute(
      "aria-describedby",
      ids.join(" "),
    );
  }

  onContainerClick(event: MouseEvent) {}

  writeValue(val: T): void {
    this.value = val;
    this.valueChange.emit(val);
  }

  registerOnChange(fn: any): void {
    this.onChange = fn;
  }

  registerOnTouched(fn: any): void {
    this.onTouched = fn;
  }

  setDisabledState(isDisabled: boolean): void {
    this.disabled = isDisabled;
  }

  ngDoCheck() {
    const control = this.ngControl
      ? (this.ngControl.control as AbstractControl)
      : null;

    this.checkUpdateErrorState(control);
    this.checkUpdateRequired(control);
  }

  /**
   * Updates the error state based on the form control
   * Taken from {@link https://github.com/angular/components/blob/a1d5614f18066c0c2dc2580c7b5099e8f68a8e74/src/material/core/common-behaviors/error-state.ts#L59}
   */
  private checkUpdateErrorState(control: AbstractControl | null) {
    const oldState = this.errorState;
    const parent = this.parentFormGroup || this.parentForm;
    const newState = this.errorStateMatcher.isErrorState(control, parent);

    if (newState !== oldState) {
      this.errorState = newState;
      this.stateChanges.next();
    }
  }

  private checkUpdateRequired(control: AbstractControl | null) {
    if (!control) {
      return;
    }

    if (
      this.required !==
      coerceBooleanProperty(control.hasValidator(Validators.required))
    ) {
      this.required = control.hasValidator(Validators.required);
    }
  }
}

results matching ""

    No results matching ""