I have created a generic input field component in Angular 17 for reuse across my application. The component works well for various input types, but I encountered an issue when using it with ng-select for edit/update functionalities.
The problem arises when I try to patch a value into the ng-select field using the [value] binding. Despite successfully patching the value, it does not display in the field.
Here's a simplified version of my CustomInputComponent template:
<div *ngIf="data?.type == 'select'" class="flex flex-col">
<label class="text-md" *ngIf="data.label">{{ data.label }}</label>
<ng-select [disabled]="data.disabled!" [class.disabled]="data.disabled!" [value]="inputValue ? inputValue : ''"
class="block {{class}} w-full text-sm text-gray-900 border border-gray-300 rounded-lg focus:ring-blue-500 focus:border-blue-500 "
[items]="data.dropdown" bindLabel="{{data.bindLabel}}" [searchable]="data.searchable ? data.searchable: false"
bindValue="{{data.bindValue}}" [formControl]="getFormControl()" (change)="onChangeEvent($event)"
[placeholder]="data.placeholder">
</ng-select>
<div class="my-2 h-1">
<div *ngIf="control?.hasError('required') && data?.required" class="text-red-500 text-xs ">{{ data.label }} is
required</div>
<div *ngIf="!control?.hasError('required') && data.customErrorMessage && !getFormControl().value"
class="text-red-500 text-xs">{{data.customErrorMessage}}</div>
</div>
</div>
import { Component, EventEmitter, Input, Output, forwardRef } from '@angular/core';
import { CommonModule } from '@angular/common';
import { CustomInput } from './custom-input.interface';
import { AbstractControl, ControlContainer, FormControl, NG_VALUE_ACCESSOR, Validators } from '@angular/forms';
import { MatInputModule } from '@angular/material/input';
import { MatDatepickerModule } from '@angular/material/datepicker';
import { MatIconModule } from '@angular/material/icon';
import { NgSelectModule } from '@ng-select/ng-select';
@Component({
selector: 'app-custom-input',
standalone: true,
imports: [CommonModule, MatInputModule, MatDatepickerModule, MatIconModule, NgSelectModule],
templateUrl: './custom-input.component.html',
styleUrl: './custom-input.component.scss',
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => CustomInputComponent),
multi: true
}
]
})
export class CustomInputComponent {
//#region Input/Output
isTooltipDate: any;
selectSixtyDays: boolean = false;
@Input() label: string;
@Input() placeholder: string;
@Input() class: string = 'dropDown_button_position';
@Input() data: CustomInput;
@Input() inputValue: string = '';
@Output() change: EventEmitter<any> = new EventEmitter();
@Output() onChange: EventEmitter<void> = new EventEmitter<void>();
@Output() dropdownButton: EventEmitter<void> = new EventEmitter<void>();
@Output() dropDownUpperButton: EventEmitter<void> = new EventEmitter<void>();
@Output() dropdownValueChange: EventEmitter<string> = new EventEmitter<string>();
//#endregion
//#region Variable Initialization
control: AbstractControl;
selectedDate: any;
//#endregion
constructor(
private controlContainer: ControlContainer,
) { }
//#region Lifecycle methods
ngOnInit() {
this.getControlContainer();
}
getControlContainer() {
if (this.controlContainer && this.data.name) {
const control = this.controlContainer.control?.get(this.data.name) as AbstractControl;
if (control) {
this.control = control;
this.getFormControl()?.addValidators([Validators.maxLength(this.data?.maxLength || 250)]);
this.getFormControl()?.updateValueAndValidity();
this.control.valueChanges.subscribe(value => {
this.inputValue = value;
});
} else {
console.error(`Control with name '${this.data.name}' not found.`);
}
}
}
getFormControl(): FormControl {
return this.data?.name ? this.controlContainer.control?.get(this.data.name) as FormControl : new FormControl();
}
onChangeEvent(event?: any) {
this.control.setValue(event ?? null)
this.change.emit(event ?? null);
this.dropdownValueChange.emit(event);
}
onChangeEventHandler(event?: any) {
this.change.emit(event ?? null);
}
onInputChange(event: Event) {
const input = event.target as HTMLInputElement;
const inputValue = input.value
// Check if the input value contains at least one non-whitespace character
let sanitizedValue = /^\s*\S/.test(inputValue) ? inputValue.replace(/[^A-Za-z ]/g, '') : '';
this.getFormControl()?.setValue(sanitizedValue);
this.onChangeEventHandler('e')
}
onNumberInputChange(event: Event, type?: string) {
const input = event.target as HTMLInputElement;
let sanitizedValue = input.value.replace(/[^0-9.]/g, '');
sanitizedValue = sanitizedValue.replace(/^-/, '');
this.getFormControl()?.setValue(sanitizedValue);
this.onChangeEventHandler('e')
}
alphaNumericInputChange(event: Event) {
const input = event.target as HTMLInputElement;
const inputValue = input.value
let sanitizedValue = /^\s*\S/.test(inputValue) ? inputValue.replace(/[^a-zA-Z0-9\s]/g, '') : '';
this.getFormControl()?.setValue(sanitizedValue);
}
stringInputChange(event: Event) {
const input = event.target as HTMLInputElement;
const inputValue = input.value
// Check if the input value contains at least one non-whitespace character
let sanitizedValue = /^\s*\S/.test(inputValue) ? inputValue : '';
this.getFormControl()?.setValue(sanitizedValue);
}
onCustomInputChange(event: Event) {
const input = event.target as HTMLInputElement;
const inputValue = input.value
if (this.data.pattern) {
const regexPattern = new RegExp(this.data.pattern, 'g');
let sanitizedValue = /^\s*\S/.test(inputValue) ? inputValue.replace(regexPattern, '') : '';
this.getFormControl()?.setValue(sanitizedValue);
}
}
//#endregion
}
<div class="sm:col-span-12">
<app-custom-input [class]="'h-10'" [inputValue]="storeForm.get('storeType').value"
[data]="{label: 'Store Type', required: true ,name:'storeType',type: 'select', searchable:true, dropdown:storeTypes, bindLabel:'name', bindValue:'name', placeholder: 'Select Store Type' }">
</app-custom-input>
</div>
The problem arises when I try to patch a value into the ng-select field using the [value] binding. Despite successfully patching the value, it does not display in the field.