src/app/core/entity/entity-type-select/entity-type-select.component.ts
BasicAutocompleteComponent<
EntityConstructor,
string
>
| changeDetection | ChangeDetectionStrategy.OnPush |
| providers |
EntityTypeSelectComponent
|
| selector | app-entity-type-select |
| standalone | true |
| imports |
MatInputModule
MatAutocompleteModule
MatCheckboxModule
MatChipInput
MatChipGrid
MatChipRow
MatTooltip
MatChipRemove
CdkVirtualScrollViewport
CdkVirtualForOf
CdkFixedSizeVirtualScroll
|
| templateUrl | ../../common-components/basic-autocomplete/basic-autocomplete.component.html |
AdminEntityFieldComponent
AdminMenuItemDetailsComponent
AdminPrimaryActionComponent
BirthdayDashboardSettingsComponent
EditEntityTypeComponent
EditMatchingEntitySideComponent
EditNewMatchActionComponent
EntityCountDashboardSettingsComponent
ImportEntityTypeComponent
NotificationRuleComponent
PermissionMatrixComponent
ReviewDuplicatesComponent
ReactiveFormsModule
MatInputModule
MatAutocompleteModule
MatCheckboxModule
NgTemplateOutlet
MatChipInput
MatChipGrid
MatChipRow
FaDynamicIconComponent
MatTooltip
MatChipRemove
DragDropModule
CdkVirtualScrollViewport
CdkVirtualForOf
CdkFixedSizeVirtualScroll
KeepPanelOpenOnSelectDirective
BasicAutocompleteComponent<
EntityConstructor,
string
>
Methods |
|
Inputs |
Outputs |
| placeholder | |
Type : any
|
|
Default value : $localize`:EntityTypeSelect placeholder:Select Record Type`
|
|
|
Inherited from
CustomFormControlDirective
|
|
| hiddenTypes | |
Type : string[]
|
|
Default value : []
|
|
|
ENTITY_TYPE keys to omit from the dropdown, e.g. types that are already selected elsewhere and should not be offered again. |
|
| optionToString | |
Type : (option: EntityConstructor) => string
|
|
Default value : (option) => option.label ?? option.ENTITY_TYPE
|
|
|
Inherited from
BasicAutocompleteComponent
|
|
| showInternalTypes | |
Type : boolean
|
|
Default value : false
|
|
|
whether to include record types without a human-readable label (usually only used internally for technical purposes) |
|
| valueMapper | |
Type : (option: EntityConstructor) => string
|
|
Default value : (option) => option.ENTITY_TYPE
|
|
|
Inherited from
BasicAutocompleteComponent
|
|
| createOption | |
Type : (input: string) => Promise<O>
|
|
|
Inherited from
BasicAutocompleteComponent
|
|
| createOptions | |
Type : CreateOptionConfig<O>[]
|
|
Default value : []
|
|
|
Inherited from
BasicAutocompleteComponent
|
|
| display | |
Type : "text" | "chips" | "none"
|
|
Default value : "text"
|
|
|
Inherited from
BasicAutocompleteComponent
|
|
|
Display the selected items as simple text, as chips or not at all (if used in combination with another component) |
|
| displayFullLengthOptionLabel | |
Type : boolean
|
|
Default value : false
|
|
|
Inherited from
BasicAutocompleteComponent
|
|
|
Whether dropdown option labels should be shown in full length. Set to false to truncate labels with ellipsis. |
|
| hideOption | |
Type : (option: O) => boolean
|
|
Default value : () => false
|
|
|
Inherited from
BasicAutocompleteComponent
|
|
| maxOptionsToDisplay | |
Type : number
|
|
Default value : 100
|
|
|
Inherited from
BasicAutocompleteComponent
|
|
|
Maximum number of options to display in the dropdown. If more options match the current filter, a hint is shown to type to narrow results. Set to 0 for no limit. Defaults to 100. |
|
| multi | |
Type : boolean
|
|
|
Inherited from
BasicAutocompleteComponent
|
|
|
Whether the user should be able to select multiple values. |
|
| options | |
Type : O[]
|
|
Default value : []
|
|
|
Inherited from
BasicAutocompleteComponent
|
|
|
The options to display in the autocomplete dropdown.
If you pass complex objects here, you can customize what value is displayed and what value is output/stored
by overriding the |
|
| reorder | |
Type : boolean
|
|
|
Inherited from
BasicAutocompleteComponent
|
|
|
Whether the user can manually drag & drop to reorder the selected items |
|
| aria-describedby | |
Type : string
|
|
|
Inherited from
CustomFormControlDirective
|
|
| disabled | |
Type : boolean
|
|
|
Inherited from
CustomFormControlDirective
|
|
| ngControl | |
Type : any
|
|
Default value : inject(NgControl, { optional: true, self: true })
|
|
|
Inherited from
CustomFormControlDirective
|
|
| required | |
Type : boolean
|
|
|
Inherited from
CustomFormControlDirective
|
|
| value | |
Type : T
|
|
|
Inherited from
CustomFormControlDirective
|
|
| autocompleteFilterChange | |
Type : (o: O) => boolean
|
|
|
Inherited from
BasicAutocompleteComponent
|
|
| valueChange | |
Type : EventEmitter
|
|
|
Inherited from
CustomFormControlDirective
|
|
| compareEnumValues |
compareEnumValues(a: any, b: any)
|
|
Inherited from
BasicAutocompleteComponent
|
|
Compare two enum values by id if present, otherwise by reference.
Returns :
boolean
|
| Async createFromConfig | ||||||
createFromConfig(marker: CreateOptionMarker<O>)
|
||||||
|
Inherited from
BasicAutocompleteComponent
|
||||||
|
Parameters :
Returns :
any
|
| Protected createOptionAriaLabel | |||||||||
createOptionAriaLabel(option: CreateOptionConfig<O>, input: string)
|
|||||||||
|
Inherited from
BasicAutocompleteComponent
|
|||||||||
|
Parameters :
Returns :
string
|
| Protected createOptionDisplay | ||||||
createOptionDisplay(input: string)
|
||||||
|
Inherited from
BasicAutocompleteComponent
|
||||||
|
Used in template to display the "Add new" option label. Delegates to optionToString so callers with a custom optionToString (e.g. showing an example date) get a preview in the "Add new" option too. Falls back to the raw input when optionToString throws or returns null/undefined (e.g. when O is an object type whose properties don't exist on a plain string).
Parameters :
Returns :
string
|
| Protected createOptionLabel | |||||||||
createOptionLabel(option: CreateOptionConfig<O>, input: string)
|
|||||||||
|
Inherited from
BasicAutocompleteComponent
|
|||||||||
|
Parameters :
Returns :
string
|
| dropChips | ||||||
dropChips(event: CdkDragDrop
|
||||||
|
Inherited from
BasicAutocompleteComponent
|
||||||
|
Parameters :
Returns :
void
|
| Protected isSelected | ||||||
isSelected(option: SelectableOption<O | V>)
|
||||||
|
Inherited from
BasicAutocompleteComponent
|
||||||
|
Whether the given option is part of the current selection. Reads the _selectedOptions signal, so that options rendered in the open dropdown are updated as soon as the selection changes.
Parameters :
Returns :
boolean
|
| onContainerClick | ||||||
onContainerClick(event: MouseEvent)
|
||||||
|
Inherited from
CustomFormControlDirective
|
||||||
|
Parameters :
Returns :
void
|
| Protected onDropdownClosed |
onDropdownClosed()
|
|
Inherited from
BasicAutocompleteComponent
|
|
The dropdown closed, which ends the selection unless the field still has the focus (e.g. after closing the dropdown with the Escape key).
Returns :
void
|
| onFocusOut | ||||||
onFocusOut(event: FocusEvent)
|
||||||
|
Inherited from
BasicAutocompleteComponent
|
||||||
|
Parameters :
Returns :
void
|
| Protected onOptionSelectionChange | ||||||
onOptionSelectionChange(event: MatOptionSelectionChange)
|
||||||
|
Inherited from
BasicAutocompleteComponent
|
||||||
|
Apply a selection the user made while the dropdown is kept open for a multi-select field. Material only emits its own
Parameters :
Returns :
void
|
| select | ||||||
select(selected: string | SelectableOption
|
||||||
|
Inherited from
BasicAutocompleteComponent
|
||||||
|
Parameters :
Returns :
void
|
| showAutocomplete | ||||||
showAutocomplete(valueToRevertTo?: string)
|
||||||
|
Inherited from
BasicAutocompleteComponent
|
||||||
|
Parameters :
Returns :
void
|
| Protected toCreateOptionValue | |||||||||
toCreateOptionValue(option: CreateOptionConfig<O>, input: string)
|
|||||||||
|
Inherited from
BasicAutocompleteComponent
|
|||||||||
|
Parameters :
Returns :
CreateOptionMarker<O>
|
| unselect | ||||||
unselect(option: SelectableOption<O | V>)
|
||||||
|
Inherited from
BasicAutocompleteComponent
|
||||||
|
Parameters :
Returns :
void
|
| Public updatePanelWidth |
updatePanelWidth()
|
|
Inherited from
BasicAutocompleteComponent
|
|
Set the width of the dropdown panel programmatically to match the parent form field. (this is not possible with pure CSS) Note: If the field is close to the viewport edge, Angular Material's overlay system may shift the dropdown horizontally to keep it visible, causing minor misalignment. This is expected and ensures accessibility.
Returns :
void
|
| writeValue | ||||||||||||
writeValue(val: V[] | V, notifyFormControl: unknown)
|
||||||||||||
|
Inherited from
CustomFormControlDirective
|
||||||||||||
|
Parameters :
Returns :
void
|
| blur |
blur()
|
|
Inherited from
CustomFormControlDirective
|
|
Returns :
void
|
| focus |
focus()
|
|
Inherited from
CustomFormControlDirective
|
|
Returns :
void
|
| registerOnChange | ||||||
registerOnChange(fn: any)
|
||||||
|
Inherited from
CustomFormControlDirective
|
||||||
|
Parameters :
Returns :
void
|
| registerOnTouched | ||||||
registerOnTouched(fn: any)
|
||||||
|
Inherited from
CustomFormControlDirective
|
||||||
|
Parameters :
Returns :
void
|
| setDescribedByIds | ||||||
setDescribedByIds(ids: string[])
|
||||||
|
Inherited from
CustomFormControlDirective
|
||||||
|
Parameters :
Returns :
void
|
| setDisabledState | ||||||
setDisabledState(isDisabled: boolean)
|
||||||
|
Inherited from
CustomFormControlDirective
|
||||||
|
Parameters :
Returns :
void
|
| Protected optionsSource |
Type : unknown
|
Default value : computed(() =>
this.availableEntityTypes(),
)
|
|
Inherited from
BasicAutocompleteComponent
|
| _selectedOptions |
Type : unknown
|
Default value : signal<SelectableOption<O, V>[]>([])
|
|
Inherited from
BasicAutocompleteComponent
|
| autocomplete |
Type : unknown
|
Default value : viewChild(MatAutocompleteTrigger)
|
|
Inherited from
BasicAutocompleteComponent
|
| autocompleteFilterFunction |
Type : function
|
|
Inherited from
BasicAutocompleteComponent
|
| autocompleteForm |
Type : unknown
|
Default value : new FormControl("")
|
|
Inherited from
BasicAutocompleteComponent
|
| autocompleteOptions |
Type : Signal<SelectableOption[]>
|
Default value : toSignal(
this.autocompleteSuggestedOptions,
{ initialValue: [] as SelectableOption<O, V>[] },
)
|
|
Inherited from
BasicAutocompleteComponent
|
| autocompleteSuggestedOptions |
Type : unknown
|
Default value : this.autocompleteForm.valueChanges.pipe(
filter((val) => typeof val === "string"),
map((val) => this.updateAutocomplete(val)),
startWith([] as SelectableOption<O, V>[]),
)
|
|
Inherited from
BasicAutocompleteComponent
|
| Protected availableCreateOptions |
Type : unknown
|
Default value : computed<CreateOptionConfig<O>[]>(() => {
if (this.createOptions().length > 0) {
return this.createOptions();
}
if (!this.createOption()) {
return [];
}
return [{ label: "", create: this.createOption() }];
})
|
|
Inherited from
BasicAutocompleteComponent
|
| effectiveDisplay |
Type : unknown
|
Default value : computed<"text" | "chips" | "none">(() =>
this.multi() && this.reorder() && this.display() === "text"
? "chips"
: this.display(),
)
|
|
Inherited from
BasicAutocompleteComponent
|
|
Derived display mode. Reorderable multi-select uses chips without mutating the public input. |
| hasMoreOptions |
Type : unknown
|
Default value : signal(false)
|
|
Inherited from
BasicAutocompleteComponent
|
| inputElement |
Type : unknown
|
Default value : viewChild("inputElement", { read: MatInput }) as Signal<
(MatInput & { _elementRef: ElementRef<HTMLElement> }) | undefined
>
|
|
Inherited from
BasicAutocompleteComponent
|
| isInSearchMode |
Type : WritableSignal<boolean>
|
Default value : signal(false)
|
|
Inherited from
BasicAutocompleteComponent
|
|
display the search input rather than the selected elements only (when the form field gets focused). |
| panelWidth |
Type : unknown
|
Default value : signal("200px")
|
|
Inherited from
BasicAutocompleteComponent
|
|
Dynamic width of the autocomplete dropdown panel. Set to match the full width of the Material form field container (including icons/padding). |
| retainSearchValue |
Type : string
|
|
Inherited from
BasicAutocompleteComponent
|
|
Keep the search value to help users quickly multi-select multiple related options without having to type filter text again |
| showAddOption |
Type : unknown
|
Default value : signal(false)
|
|
Inherited from
BasicAutocompleteComponent
|
|
whether the "add new" option is logically allowed in the current context (e.g. not creating a duplicate) |
| templateRef |
Type : unknown
|
Default value : contentChild(TemplateRef)
|
|
Inherited from
BasicAutocompleteComponent
|
| trackByOptionValueFn |
Type : TrackByFunction<SelectableOption<O, V>> | undefined
|
Default value : () => {...}
|
|
Inherited from
BasicAutocompleteComponent
|
| viewportHeight |
Type : unknown
|
Default value : computed(() => {
const contentHeight = (this.autocompleteOptions()?.length ?? 0) * 48;
const availableHeight =
BasicAutocompleteComponent.PANEL_MAX_HEIGHT -
BasicAutocompleteComponent.FOOTER_RESERVE;
return Math.min(contentHeight, availableHeight);
})
|
|
Inherited from
BasicAutocompleteComponent
|
| virtualScrollViewport |
Type : unknown
|
Default value : viewChild(CdkVirtualScrollViewport)
|
|
Inherited from
BasicAutocompleteComponent
|
| controlType |
Type : string
|
Default value : "custom-control"
|
|
Inherited from
CustomFormControlDirective
|
| elementRef |
Type : unknown
|
Default value : inject<ElementRef<HTMLElement>>(ElementRef)
|
|
Inherited from
CustomFormControlDirective
|
| Readonly enabled |
Type : Signal<boolean>
|
Default value : computed(() => !this._disabled())
|
|
Inherited from
CustomFormControlDirective
|
|
Whether the control is currently enabled, as a signal (tracks disabled). |
| errorStateMatcher |
Type : unknown
|
Default value : inject(ErrorStateMatcher)
|
|
Inherited from
CustomFormControlDirective
|
| id |
Type : unknown
|
Default value : `custom-form-control-${CustomFormControlDirective.nextId++}`
|
|
Inherited from
CustomFormControlDirective
|
| Static nextId |
Type : number
|
Default value : 0
|
|
Inherited from
CustomFormControlDirective
|
| onChange |
Type : unknown
|
Default value : () => {...}
|
|
Inherited from
CustomFormControlDirective
|
| onTouched |
Type : unknown
|
Default value : () => {...}
|
|
Inherited from
CustomFormControlDirective
|
| parentForm |
Type : unknown
|
Default value : inject(NgForm, { optional: true })
|
|
Inherited from
CustomFormControlDirective
|
| parentFormGroup |
Type : unknown
|
Default value : inject(FormGroupDirective, { optional: true })
|
|
Inherited from
CustomFormControlDirective
|
| stateChanges |
Type : unknown
|
Default value : new Subject<void>()
|
|
Inherited from
CustomFormControlDirective
|
| Readonly valueSignal |
Type : Signal<T>
|
Default value : computed(() => this._value())
|
|
Inherited from
CustomFormControlDirective
|
|
The current value of the control as a signal.
Authoritative in both modes: it reflects the bound |
import {
ChangeDetectionStrategy,
Component,
computed,
inject,
Input,
input,
} from "@angular/core";
import {
BASIC_AUTOCOMPLETE_COMPONENT_IMPORTS,
BasicAutocompleteComponent,
} from "../../common-components/basic-autocomplete/basic-autocomplete.component";
import { EntityConstructor } from "../model/entity";
import { EntityRegistry } from "../database-entity.decorator";
import { MatFormFieldControl } from "@angular/material/form-field";
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
selector: "app-entity-type-select",
templateUrl:
"../../common-components/basic-autocomplete/basic-autocomplete.component.html",
imports: BASIC_AUTOCOMPLETE_COMPONENT_IMPORTS,
providers: [
{ provide: MatFormFieldControl, useExisting: EntityTypeSelectComponent },
],
})
export class EntityTypeSelectComponent extends BasicAutocompleteComponent<
EntityConstructor,
string
> {
// `placeholder` is part of the `MatFormFieldControl` contract (Angular Material
// reads it as a plain property), so it stays a decorator `@Input()` rather than a
// signal input; here we only override the default value.
@Input() override placeholder =
$localize`:EntityTypeSelect placeholder:Select Record Type`;
/**
* whether to include record types without a human-readable label
* (usually only used internally for technical purposes)
*/
showInternalTypes = input(false);
/**
* ENTITY_TYPE keys to omit from the dropdown, e.g. types that are already
* selected elsewhere and should not be offered again.
*/
hiddenTypes = input<string[]>([]);
private readonly entityRegistry = inject(EntityRegistry);
private readonly availableEntityTypes = computed(() =>
this.entityRegistry
.getEntityTypes(!this.showInternalTypes())
.map(({ value }) => value)
.filter((type) => !this.hiddenTypes().includes(type.ENTITY_TYPE)),
);
protected override optionsSource = computed(() =>
this.availableEntityTypes(),
);
override optionToString = input<(option: EntityConstructor) => string>(
(option) => option.label ?? option.ENTITY_TYPE,
);
override valueMapper = input<(option: EntityConstructor) => string>(
(option) => option.ENTITY_TYPE,
);
}
<div matAutocompleteOrigin #autocompleteOrigin="matAutocompleteOrigin">
<!--Display-->
@if (effectiveDisplay() === "text" || effectiveDisplay() === "none") {
<input
[id]="id"
[hidden]="isInSearchMode() || effectiveDisplay() === 'none'"
[disabled]="!enabled()"
matInput
style="text-overflow: ellipsis; width: calc(100% - 50px)"
(focusin)="showAutocomplete()"
(focusout)="showAutocomplete()"
[value]="displayText"
[placeholder]="placeholder"
/>
} @else {
<input
[hidden]="true"
[disabled]="!enabled()"
matInput
(focusin)="showAutocomplete()"
(focusout)="showAutocomplete()"
[matChipInputFor]="chipList"
/>
<mat-chip-grid
#chipList
cdkDropList
cdkDropListOrientation="mixed"
[cdkDropListDisabled]="!reorder()"
(cdkDropListDropped)="dropChips($event)"
class="chip-drop-list"
>
@for (item of _selectedOptions(); track item.asValue) {
<mat-chip-row
cdkDrag
[cdkDragDisabled]="!reorder()"
[editable]="enabled()"
class="chip"
[style.background-color]="item.asValue?.['color']"
>
<ng-template
[ngTemplateOutlet]="chipContent"
[ngTemplateOutletContext]="{ $implicit: item }"
></ng-template>
@if (enabled()) {
<button matChipRemove (click)="unselect(item)">
<app-fa-dynamic-icon
i18n-matTooltip="
tooltip for remove icon on chips of dropdown item
"
matTooltip="Remove"
icon="xmark"
></app-fa-dynamic-icon>
</button>
}
<ng-template cdkDragPreview>
<mat-chip-row
[editable]="false"
class="chip chip-preview"
[style.background-color]="item.asValue?.['color']"
>
<ng-template
[ngTemplateOutlet]="chipContent"
[ngTemplateOutletContext]="{ $implicit: item }"
></ng-template>
</mat-chip-row>
</ng-template>
</mat-chip-row>
}
</mat-chip-grid>
}
<!--Search-->
<input
[hidden]="!isInSearchMode()"
#inputElement
[formControl]="autocompleteForm"
matInput
style="text-overflow: ellipsis"
[matAutocomplete]="autoSuggestions"
[appKeepPanelOpenOnSelect]="!!multi()"
[matAutocompleteConnectedTo]="autocompleteOrigin"
(focusout)="onFocusOut($event)"
[placeholder]="placeholder"
/>
</div>
<!--
Autocomplete
-->
<mat-autocomplete
[disableRipple]="true"
#autoSuggestions="matAutocomplete"
(optionSelected)="select($event.option.value)"
(closed)="onDropdownClosed()"
autoActiveFirstOption
[hideSingleSelectionIndicator]="true"
[panelWidth]="panelWidth()"
>
<!-- Select All and Clear Buttons on top of options via content projection-->
<div>
<ng-content select="[autocompleteHeader]"></ng-content>
</div>
<div>
@if (!displayFullLengthOptionLabel()) {
<cdk-virtual-scroll-viewport
[style.height.px]="viewportHeight()"
[itemSize]="48"
minBufferPx="200"
maxBufferPx="500"
>
<mat-option
[value]="item"
(onSelectionChange)="onOptionSelectionChange($event)"
*cdkVirtualFor="
let item of autocompleteOptions();
trackBy: trackByOptionValueFn
"
>
<ng-container
[ngTemplateOutlet]="optionContent"
[ngTemplateOutletContext]="{ $implicit: item, truncate: true }"
></ng-container>
</mat-option>
</cdk-virtual-scroll-viewport>
} @else {
@for (item of autocompleteOptions(); track item.asValue) {
<mat-option
[value]="item"
class="option-with-wrap"
(onSelectionChange)="onOptionSelectionChange($event)"
>
<ng-container
[ngTemplateOutlet]="optionContent"
[ngTemplateOutletContext]="{ $implicit: item, truncate: false }"
></ng-container>
</mat-option>
}
}
</div>
<ng-template #chipContent let-item>
@if (reorder()) {
<span class="drag-handle color-accent" cdkDragHandle>
<app-fa-dynamic-icon icon="grip-vertical"></app-fa-dynamic-icon>
</span>
}
@if (!templateRef()) {
{{ item.asString }}
} @else {
<ng-template
[ngTemplateOutlet]="templateRef()"
[ngTemplateOutletContext]="{ $implicit: item.initial }"
></ng-template>
}
</ng-template>
<ng-template #optionContent let-item let-truncate="truncate">
<div
class="option-content flex-row disable-autocomplete-active-color align-center"
>
@if (multi()) {
<mat-checkbox [checked]="isSelected(item)"></mat-checkbox>
}
@if (!templateRef()) {
<span
class="option-label"
[class.text-truncate]="truncate"
[class.text-wrap]="!truncate"
[matTooltip]="item.asString"
matTooltipPosition="above"
[class.not-defined-label]="item.isEmpty"
[class.invalid-label]="item.isInvalid"
>
{{ item.asString }}
</span>
} @else {
<div class="option-label item-option">
<ng-template
[ngTemplateOutlet]="templateRef()"
[ngTemplateOutletContext]="{ $implicit: item.initial }"
></ng-template>
</div>
}
</div>
</ng-template>
<!-- Create new option -->
@if (showAddOption() && inputElement.value) {
@for (option of availableCreateOptions(); track $index) {
<mat-option
[value]="toCreateOptionValue(option, inputElement.value)"
[attr.aria-label]="createOptionAriaLabel(option, inputElement.value)"
(onSelectionChange)="onOptionSelectionChange($event)"
>
<em
i18n="
Label for adding an option in a dropdown|e.g. Add new My new Option
"
>Add new</em
>
{{ createOptionLabel(option, inputElement.value) }}
</mat-option>
}
}
<mat-option style="display: none">
<!-- This mat-option is never displayed ("display: none") but has to be there,
because the footer below will only be displayed with at least one mat-option -->
</mat-option>
<div class="autocomplete-footer">
@if (hasMoreOptions()) {
<em
class="more-options-hint"
i18n="Hint shown when dropdown has more options than displayed"
>Type to filter more results ...</em
>
}
<ng-content select="[autocompleteFooter]"></ng-content>
</div>
</mat-autocomplete>