npm.io
21.0.0 • Published 4 months ago

@eqproject/eqp-select

Licence
MIT
Version
21.0.0
Deps
2
Size
168 kB
Vulns
0
Weekly
0

Table of contents

Required

  • Angular Material installed and imported
  • @eqproject/eqp-lookup installed (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). includeFullObject is ignored.
  • arrayData with primitives (strings/numbers): always emits the primitive value. includeFullObject is ignored.
  • arrayData with objects, includeFullObject = true (default): emits the full item object.
  • arrayData with objects, includeFullObject = false: emits only item[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