@eqproject/eqp-select
Table of contents
Required
- Angular Material installed and imported
-
@eqproject/eqp-lookupinstalled (eqp-select delegates rendering to eqp-lookup internally)
Getting started
Step 1: Install eqp-select:
NPM
npm install --save @eqproject/eqp-select
Step 2: Import the EqpSelectModule and install :
import { EqpSelectModule } from '@eqproject/eqp-select';
@NgModule({
declarations: [AppComponent],
imports: [EqpSelectModule],
bootstrap: [AppComponent]
})
export class AppModule {}
API
Inputs
| Input | Type | Default | Required | Description |
|---|---|---|---|---|
| [placeholder] | string |
- |
no | Label shown as placeholder inside select. |
| [enumData] | any |
- |
no | Enum property to use in order to create a key-value array. Internally converted to { ID, Label } pairs. When enumData is used, includeFullObject is ignored and the enum key (integer) is always emitted. |
| [enumDataToExclude] | Array<any> |
- |
no | Array of elements to exclude among those included inside the given Enum. Exclusion is performed by matching the key property (arrayKeyProperty) of each generated enum entry. |
| [arrayData] | Array<any> |
- |
no | Array of objects that select will show. The structure can be {keyProperty: '', valueProperty: ''} or an array of primitive elements. When primitives are passed, the component automatically wraps each as { ID: element, Label: element } so that arrayKeyProperty and arrayValueProperty do not need to be redefined. |
| [arrayKeyProperty] | string |
'ID' |
no | Property name to use as the bound key. Supports dot-notation paths (e.g. 'Company.Id') to access nested object properties. Must be 'ID' when arrayData is an array of primitive elements. |
| [arrayValueProperty] | string |
'Label' |
no | Property name to use as the displayed label. Supports dot-notation paths (e.g. 'Company.Name') to access nested object properties. Must be 'Label' when arrayData is an array of primitive elements. |
| [ngModelOptions] | any |
- |
no | Options to pass to ngModel. |
| [ngModelInput] | any |
- |
no | Binded property of object. Supports two-way binding with [(ngModelInput)]. When this value changes at runtime (and differs from the previous value), the component automatically refreshes its internal selection. |
| [formGroupInput] | any |
- |
no | FormGroup name, in case of formControlNameInput, that property need to be repeated for EVERY eqp-select used. |
| [formControlNameInput] | any |
- |
no | formControlName defined in ts formGroup control. When provided together with formGroupInput, the component subscribes to statusChanges to keep isDisabled in sync with the FormControl's disabled state. |
| [isRequired] | boolean |
false |
no | Set select as required. |
| [isDisabled] | boolean |
false |
no | Disable the select. When a FormControl is used, the disabled state is also kept in sync automatically via statusChanges. |
| [isMultiLanguage] | boolean |
false |
no | If true, see section and examples about use, if not interested in use, set to false or don't write it. Requires the TranslateService instance to be passed to TranslateSelectHelper.loadTranslateService() before the component initializes. |
| [multilanguagePrefixKey] | string |
- |
no | Prefix of the key used to translate the property. The full translation key is built as multilanguagePrefixKey + enumValue. Include any separators (e.g. 'ENUMS.GENDER.'). |
| [isAlphabeticalOrderable] | boolean |
false |
no | Define if the datasource will be ordered alphabetically. Alphabetical ordering is also re-applied after each search when isSearchable is true. |
| [suffixIcon] | string |
- |
no | String containing the icon (mat-icon) that will appear after the label of the selected object(s). Visible in the selected-item chip inside the input. |
| [prefixIcon] | string |
- |
no | String containing the icon (mat-icon) that will appear before the label of the selected object(s). Visible in the selected-item chip inside the input. |
| [isMultiSelect] | boolean |
false |
no | Define if the Select allows multiple selection. In multi-select mode the value container has a maximum height of 90px with vertical scroll so that it does not expand the form layout unboundedly. |
| [showCancelButton] | boolean |
true |
no | Property to define if the button to clear the selection is visible. In multi-select mode, clears the array (setting it to empty); in single-select mode, sets the value to null. |
| [isSearchable] | boolean |
false |
no | Defines if it's possible to type inside select and search among items. The search filters arrayValueProperty (including nested dot-notation paths). During search, onSearching is set to true to show a loading state. |
| [noResultSearchText] | string |
'Nessun elemento trovato' |
no | Custom text when filter returns empty result. |
| [isReadonly] | boolean |
false |
no | Set select as readonly. |
| [isSearchWhileComposing] | boolean |
true |
no | Whether items should be filtered while composition started (IME input). |
| [appendToInput] | string |
- |
no | Input used when the select is contained inside a dialog or tab and there are visualization problems. Useful value: 'body'. |
| [dropdownPosition] | 'auto' | 'top' | 'bottom' |
'auto' |
no | Specifies the position of the dropdown. |
| [selectOnTab] | boolean |
true |
no | Select marked dropdown item using tab. |
| [selectAll] | boolean |
false |
no | Add an option inside the dropdown that allows the user to select all the elements. |
| [selectAllText] | string |
'Seleziona tutto' |
no | Text displayed in the select all option. |
| [selectableGroupAsModel] | boolean |
false |
no | Indicates whether to select all children or group itself when the select-all option is used. |
| [clearAllText] | string |
'Elimina' |
no | Text displayed as tooltip when hovering the clear button. |
| [customOption] | TemplateRef<any> |
- |
no | Custom template for the option inside the dropdown panel. The template context exposes item and index. |
| [customLabel] | TemplateRef<any> |
- |
no | Custom template for the selected item's label. When prefixIcon or suffixIcon is set and no customLabel is provided, the component uses an internal template that renders the icons alongside the label and an optional Total badge. |
| [includeFullObject] | boolean |
true |
no | When true (default), the full item object from arrayData is emitted on selection. When false, only the key value (arrayKeyProperty) is emitted. This input is ignored when enumData is used (the enum key is always emitted) and when arrayData is a primitive array (the primitive value itself is always emitted). |
| [appearance] | string |
'' |
no | Controls the Angular Material form-field appearance. Accepts the same values as mat-form-field ('outline', 'fill', 'standard'). Passed through to the underlying eqp-lookup. |
| [class] | string |
- |
no | CSS class applied to the ng-select element for custom styling. |
N.B. One of enumData or arrayData is required.
Outputs
| Output | Event arguments | Required | Description |
|---|---|---|---|
| (ngModelInputChange) | any |
- |
Invoked when the value changes and the ngModelInput is binded. When items are grouped (selectAll) it returns the group 'exploded'. The emitted value respects the includeFullObject setting. |
| (formControlChange) | any |
- |
Invoked when the value changes and a form control is binded. Also marks the FormControl as dirty and touched. When items are grouped (selectAll) it returns the group 'exploded'. |
Public methods (accessible via @ViewChild)
| Method | Description |
|---|---|
reloadData() |
Re-initializes the internal datasource from the current arrayData or enumData. Call this when the datasource changes programmatically. Triggers change detection after a 50ms delay to allow the underlying ng-select to re-render. |
Reload data
When your datasource changes, you can update the datasource inside the select calling the function "reloadData", as follows:
reload() {
this.selectToReload.reloadData();
}
It will work for both arrayData and enumData changes.
Use cases
Case 1: Select with arrayData and ngModelInput
Define selector in html for normal use
<eqp-select
[arrayData]="dataSource"
[isSearchable]="true"
[(ngModelInput)]="selectWithArrayData"
placeholder="Seleziona elemento"
(ngModelInputChange)="selectChange($event)"
[includeFullObject]="false"
[arrayKeyProperty]="'Key'"
[arrayValueProperty]="'Description'">
</eqp-select>
Define variables and functions in ts file
dataSource : Array<{Key: string, Description: string}>;
selectWithArrayData: any;
...
selectChange(event) {
// Body
}
Case 2: Multi-select with enumData and form control
Define selector in HTML
<eqp-select
[enumData]="testEnum"
[isMultiSelect]="true"
placeholder="Seleziona elemento"
(formControlChange)="selectChange($event)"
[formGroupInput]="formGroup"
[formControlNameInput]="'selectWithEnumData'">
</eqp-select>
Define variables and functions in ts file
testEnum = TestEnum;
formGroup: FormGroup;
...
createForm() {
this.formGroup = this.formBuilder.group({
selectWithEnumData: []
})
}
...
selectChange(event) {
// Body
}
Case 3: Multi-select with select all
Define selector in HTML
<eqp-select
[enumData]="testEnum"
(formControlChange)="selectChange($event)"
[formGroupInput]="formGroup"
[formControlNameInput]="'selectWithEnumDataAndSelectAll'"
[isMultiSelect]="true"
[selectAll]="true"
[selectableGroupAsModel]="true"
placeholder="Seleziona elemento">
</eqp-select>
Define variables and functions in ts file
testEnum = TestEnum;
formGroup: FormGroup;
...
createForm() {
this.formGroup = this.formBuilder.group({
selectWithEnumDataAndSelectAll: []
})
}
...
selectChange(event) {
// Body
}
Case 4: Select with primitive array
When arrayData is a plain array of strings or numbers, the component automatically wraps each element as { ID: element, Label: element }. There is no need to define arrayKeyProperty or arrayValueProperty.
<eqp-select
[arrayData]="['Option A', 'Option B', 'Option C']"
[(ngModelInput)]="selectedValue"
placeholder="Scegli un'opzione">
</eqp-select>
Case 5: Select with nested property paths
Both arrayKeyProperty and arrayValueProperty support dot-notation to access nested object properties.
<eqp-select
[arrayData]="employees"
[arrayKeyProperty]="'Person.Id'"
[arrayValueProperty]="'Person.FullName'"
[(ngModelInput)]="selectedEmployee"
[includeFullObject]="true"
placeholder="Seleziona dipendente">
</eqp-select>
employees = [
{ Person: { Id: 1, FullName: 'Mario Rossi' }, Department: 'IT' },
{ Person: { Id: 2, FullName: 'Luigi Bianchi' }, Department: 'HR' }
];
Case 6: Select with custom option and label templates
Use customOption and customLabel to fully control how items are rendered in the dropdown and the selection input respectively.
<ng-template #optionTpl let-item="item">
<div class="d-flex align-items-center gap-2">
<mat-icon>person</mat-icon>
<span>{{ item.FullName }}</span>
<small class="text-muted">{{ item.Department }}</small>
</div>
</ng-template>
<ng-template #labelTpl let-item="item" let-clear="clear">
<span>{{ item.FullName }}</span>
<span class="ng-value-icon right" (click)="clear(item)">×</span>
</ng-template>
<eqp-select
[arrayData]="employees"
[arrayKeyProperty]="'Id'"
[arrayValueProperty]="'FullName'"
[customOption]="optionTpl"
[customLabel]="labelTpl"
[(ngModelInput)]="selectedEmployee"
placeholder="Seleziona">
</eqp-select>
Case 7: Select with prefix/suffix icons
When prefixIcon or suffixIcon is defined, the component uses an internal label template that renders Material icons alongside the label. If an item also has a Total property, a secondary badge is shown next to the label.
<eqp-select
[arrayData]="categories"
[arrayKeyProperty]="'ID'"
[arrayValueProperty]="'Name'"
[prefixIcon]="'label'"
[suffixIcon]="'arrow_right'"
[(ngModelInput)]="selectedCategory"
placeholder="Categoria">
</eqp-select>
Case 8: Select with excluded enum values
Use enumDataToExclude to hide specific enum values from the list while keeping the rest.
<eqp-select
[enumData]="statusEnum"
[enumDataToExclude]="[StatusEnum.Deleted, StatusEnum.Archived]"
[(ngModelInput)]="selectedStatus"
placeholder="Stato">
</eqp-select>
Case 9: Select emitting only the key value
By default, eqp-select emits the full item object when arrayData is used. Set [includeFullObject]="false" to emit only the key value instead.
<eqp-select
[arrayData]="users"
[arrayKeyProperty]="'ID'"
[arrayValueProperty]="'FullName'"
[includeFullObject]="false"
(ngModelInputChange)="onUserSelected($event)"
placeholder="Utente">
</eqp-select>
onUserSelected(userId: number) {
// event is just the ID, not the full user object
}
Multi-language
To use multilanguage, set isMultiLanguage to true and pass a prefix string that matches the path in your translation JSON. Before the component initializes, inject the TranslateService instance into the TranslateSelectHelper singleton:
constructor(private translateSelectHelper: TranslateSelectHelper, private translate: TranslateService) {}
ngOnInit(): void {
this.translateSelectHelper.loadTranslateService(this.translate);
}
Then use the component with the multilanguagePrefixKey pointing to the correct section of your JSON:
<eqp-select
[enumData]="testEnum"
[isMultiSelect]="true"
placeholder="Seleziona elemento"
(formControlChange)="selectChange($event)"
[formGroupInput]="formGroup"
[formControlNameInput]="'selectWithEnumData'"
[isMultiLanguage]="true"
[multilanguagePrefixKey]="'ENUMS.TEST.'">
</eqp-select>
The translation key for each enum entry is built as multilanguagePrefixKey + enumStringValue. For example, given prefix 'ENUMS.TEST.' and an enum value 'Active', the lookup key will be 'ENUMS.TEST.Active'.
If isMultiLanguage is true but TranslateService has not been injected into TranslateSelectHelper, the component throws an error at init: Configurazione multilingua errata. Passare l'istanza del translateService.
Notes on behavior
ControlValueAccessor support
eqp-select implements ControlValueAccessor and registers itself with NG_VALUE_ACCESSOR. This means it can be used directly with [(ngModel)] or [formControl] in addition to the formGroupInput/formControlNameInput pair.
Value extraction logic (includeFullObject, enumData, primitive arrays)
The value that is emitted by ngModelInputChange and formControlChange follows this logic:
- enumData: always emits the enum key (integer).
includeFullObjectis ignored. - arrayData with primitives (strings/numbers): always emits the primitive value.
includeFullObjectis ignored. - arrayData with objects,
includeFullObject = true(default): emits the full item object. - arrayData with objects,
includeFullObject = false: emits onlyitem[arrayKeyProperty].
Initial value resolution
On ngAfterViewInit, the component reads the initial value from either ngModelInput or the bound FormControl and resolves the corresponding full object(s) from listOfElements by matching on arrayKeyProperty. This ensures that pre-populated forms display the correct label rather than a raw key.
If the initial value was a primitive key (e.g. an ID from a REST response) and includeFullObject is true, the component also re-emits ngModelInputChange with the resolved full object so the parent has access to all item properties from the start.
Automatic datasource refresh on input changes
ngOnChanges watches both arrayData and ngModelInput using deep equality (JSON.stringify comparison). If either changes after initialization, the datasource or selected value is automatically updated without requiring a manual reloadData() call. Manual reloadData() is only needed when you need to force a refresh for other reasons (e.g. after an async operation that doesn't change the reference).
arrayKeyProperty validation
If enumData is not set and arrayKeyProperty evaluates to an empty/falsy value at init time, the component throws: La proprietà arrayKeyProperty è obbligatoria. Since the default is 'ID', this only occurs if the property is explicitly set to null or an empty string.
FormControl disabled state sync
When formGroupInput and formControlNameInput are provided, the component subscribes to the FormControl's statusChanges observable and keeps isDisabled in sync. This means disabling the control programmatically via formGroup.get('key').disable() will immediately reflect in the visual state of the select without needing to rebind [isDisabled].
Multi-select value container
In multi-select mode (isMultiSelect = true), the selected-value container is capped at 90px height with vertical overflow-y scroll. This prevents the input from growing to fill the page when many items are selected.
Credits
This library has been developed by EqProject SRL, for more info contact: info@eqproject.it