src/app/core/common-components/basic-autocomplete/custom-form-control.directive.ts
import {
AbstractControl,
ControlValueAccessor,
FormGroupDirective,
NgControl,
NgForm,
Validators,
} from "@angular/forms";
import { MatFormFieldControl } from "@angular/material/form-field";
import {
Directive,
DoCheck,
ElementRef,
HostBinding,
Input,
OnDestroy,
} from "@angular/core";
import { Subject } from "rxjs";
import { coerceBooleanProperty } from "@angular/cdk/coercion";
import { ErrorStateMatcher } from "@angular/material/core";
@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;
abstract inputElement: { _elementRef: ElementRef<HTMLElement> };
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) {
this._value = value;
this.stateChanges.next();
}
_value: T;
constructor(
public elementRef: ElementRef<HTMLElement>,
public errorStateMatcher: ErrorStateMatcher,
public ngControl: NgControl,
public parentForm: NgForm,
public parentFormGroup: FormGroupDirective,
) {
if (this.ngControl != null) {
this.ngControl.valueAccessor = this;
}
}
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.inputElement._elementRef.nativeElement.setAttribute(
"aria-describedby",
ids.join(" "),
);
}
abstract onContainerClick(event: MouseEvent);
writeValue(val: T): void {
this.value = 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);
}
}
}