Use selection modals when users need to select options from a long or unpredictable list and then immediately interact with the selections. If users only need to select options, use the lookup component instead.
Use buttons to open selection modals. After users select options, display selections in a format or pattern that supports the next interaction.
Don't use selection modals when users only need to select options. Instead, use lookup fields.
Don't use selection modals for five or fewer options. Instead, use checkboxes or radio buttons.
Don't use selection modals when a list of options requires a format other than a simple selectable repeater. If a list requires a different format, such as a custom repeater template or a tree view, or if users require filters to find and select options, create a custom modal with the appropriate features.
Use single-select mode to limit users to a single option, and use multiselect mode to let them select multiple options.
If users may need to add options to the list, include a button to start the workflow to create options. After users create options, they are selected in the selection modal.
Long lists inside selection modals use infinite scroll.
Selection modals don't provide a default presentation of selected options. When users close selection modals, present the selected options in a format that matches the needs of the scenario.
Use the following format for selection modal titles, and use plural objects when using multiselect:
Select <direct object>
Use Select as the standard label for the primary button. Use Cancel as the standard label for the link button.
@skyux/lookup
View in NPM | View in GitHub None found. npm install --save-exact @skyux/lookup
None found. Displays a modal for selecting one or more values.
open(args: SkySelectionModalOpenArgs): SkySelectionModalInstance
Opens the selection modal.
args: SkySelectionModalOpenArgs
Parameters for the selection modal.
Represents an instance of a selection modal.
closed: Observable<SkySelectionModalCloseArgs>
An event that the selection modal instance emits when it closes.
It emits a SkySelectionModalCloseArgs
object with a selectedItems
property that includes
items selected by users on close or save and a reason
property that indicates
whether the selection modal was saved or closed without saving.
The reason
property accepts "cancel"
, "close"
, and "save"
.
type SkyLookupSelectModeType = "single" | "multiple"
Specifies the information for the callback used when adding a new item to a selection modal instance.
interface SkySelectionModalAddCallbackArgs {
item: any;
}
item: any
The new item which has been added to the data. This item will be automatically selected.
Specifies a callback function for the consumer to use to notify the selection modal that a new item has been added.
interface SkySelectionModalAddClickEventArgs {
itemAdded: (args: SkySelectionModalAddCallbackArgs) => void;
}
itemAdded: (args: SkySelectionModalAddCallbackArgs) => void
A callback function for the consumer to use to notify the selection modal that a new item has been added.
The result from the selection modal.
interface SkySelectionModalCloseArgs {
reason: "cancel" | "close" | "save";
selectedItems?: unknown[];
}
reason: "cancel" | "close" | "save"
Indicates why the selection modal was closed.
selectedItems?: unknown[]
A collection of items the user selected. This property is only
set when the result
property is set to save
.
Parameters for the selection modal.
interface SkySelectionModalOpenArgs {
addClick?: (args: SkySelectionModalAddClickEventArgs) => void;
descriptorProperty: string;
idProperty: string;
initialSearch?: string;
itemTemplate?: TemplateRef<unknown>;
searchAsync: (args: SkySelectionModalSearchArgs) => undefined | Observable<SkySelectionModalSearchResult>;
selectionDescriptor?: string;
selectMode: SkyLookupSelectModeType;
showAddButton?: boolean;
title?: string;
value?: unknown[];
wrapperClass?: string;
}
addClick?: (args: SkySelectionModalAddClickEventArgs) => void
Called when users select the button to add options to the list.
descriptorProperty: string
Specifies an object property to display in the text input after users select an item in the dropdown list.
idProperty: string
An object property that represents the object's unique identifier.
initialSearch?: string
The initial search text.
itemTemplate?: TemplateRef<unknown>
The template to format each option in the search results. The selection modal
injects values into the template as item
variables that reference all the object
properties of the options. If you do not specify a template, the item's descriptor
property value is displayed.
searchAsync: (args: SkySelectionModalSearchArgs) => undefined | Observable<SkySelectionModalSearchResult>
Called when users enter new search information and returns results via an observable.
selectionDescriptor?: string
A descriptor for the item or items being selected. Use a plural term when selectMode
is set to multiple
; otherwise, use a singular term. The descriptor helps set the selection modal's aria-label
attributes for the multiselect toolbar controls, the search input, and the save button to provide text equivalents for screen readers to support accessibility.
For example, when the descriptor is "constituents," the search input's aria-label
is "Search constituents." For more information about the aria-label
attribute, see the WAI-ARIA definition.
selectMode: SkyLookupSelectModeType
Specifies whether users can select one option or multiple options.
showAddButton?: boolean
Whether to display a button that lets users add options to the list.
title?: string
selectionDescriptor
input to give context to the title and accessibility labels instead.
The title for the selection modal.
value?: unknown[]
The initial value for the selection modal.
wrapperClass?: string
The CSS class to add to the modal, such as ag-custom-component-popup
for
using a modal as part of a cell editor in Data Entry Grid.
The result from the selection modal.
interface SkySelectionModalResult {
result: "cancel" | "save";
selectedItems?: unknown[];
}
result: "cancel" | "save"
Indicates whether the user saved or canceled the modal.
selectedItems?: unknown[]
A collection of items the user selected. This property is only
set when the result
property is set to save
.
Arguments passed when an asynchronous search is executed from the selection modal service.
interface SkySelectionModalSearchArgs {
continuationData?: unknown;
offset: number;
searchText: string;
}
continuationData?: unknown
A continuation token which can be set and then will be passed back with any future searches. This is helpful for applications which utilize a token instead of an offset when fetching data.
offset: number
The offset index of the first result to return. When search is executed as a result of an infinite scroll event, for example, offset will be set to the number of items already displayed.
searchText: string
The search text entered by the user.
The result of searching for items to display in a selection modal.
interface SkySelectionModalSearchResult {
continuationData?: unknown;
hasMore?: boolean;
items: unknown[];
totalCount: number;
}
continuationData?: unknown
Data provided on "load more" search result requests. Use this property for information such as a continuation token for paged database queries.
hasMore?: boolean
Indicates whether there are more results that match the search criteria.
items: unknown[]
A list of items matching the search criteria. When there are more items that match
the search criteria, set the hasMore
property to true
more records can be lazy-loaded
as the user scrolls through the search results.
totalCount: number
The total number of records that match the search criteria, including items not returned in the current list of items.
SKY UX test harnesses are built upon Angular CDK component harnesses. For more information see the Angular CDK component harness documentation.
import { SkySelectionModalHarness } from '@skyux/lookup/testing';
Harness for interacting with a selection modal in tests.
cancel(): Promise<void>
Closes the picker without saving any selections made.
Promise<void>
clearAll(): Promise<void>
Clears all selections made.
Promise<void>
clearSearchText(): Promise<void>
Clears the text of the search input.
Promise<void>
clickAddButton(): Promise<void>
Clicks the add button.
Promise<void>
enterSearchText(value: string): Promise<void>
Enters text into the search input and performs a search.
value: string
Promise<void>
getClearAllButtonAriaLabel(): Promise<null | string>
Gets the clear all button's aria-label.
Promise<null | string>
getOnlyShowSelectedAriaLabel(): Promise<null | string>
Gets the "Only show selected" checkbox's aria-label
Promise<null | string>
getSaveButtonAriaLabel(): Promise<null | string>
Gets the save button's aria-label.
Promise<null | string>
getSearchAriaLabel(): Promise<null | string>
Gets the search input's aria-label.
Promise<null | string>
getSearchResults(filters?: SkySelectionModalSearchResultHarnessFilters): Promise<SkySelectionModalSearchResultHarness[]>
Gets a list of search results.
filters?: SkySelectionModalSearchResultHarnessFilters
Promise<SkySelectionModalSearchResultHarness[]>
getSelectAllButtonAriaLabel(): Promise<null | string>
Gets the select all button's aria-label.
Promise<null | string>
hasAddButton(): Promise<boolean>
Whether the selection modal is configured to show the add button.
Promise<boolean>
isMultiselect(): Promise<boolean>
Whether the selection modal is configured to allow multiple selections.
Promise<boolean>
loadMore(): Promise<void>
Loads more results in the picker.
Promise<void>
saveAndClose(): Promise<void>
Saves any selections made and closes the modal.
Promise<void>
selectAll(): Promise<void>
Selects all search results.
Promise<void>
selectSearchResult(filters?: SkySelectionModalSearchResultHarnessFilters): Promise<void>
Selects multiple search results based on a set of criteria.
filters?: SkySelectionModalSearchResultHarnessFilters
Promise<void>
import { SkySelectionModalSearchResultHarness } from '@skyux/lookup/testing';
Harness for interacting with a selection modal's search results in tests.
click(): Promise<void>
Clicks on the repeater item.
Promise<void>
collapse(): Promise<void>
Collapses the repeater item, or does nothing if already collapsed.
Promise<void>
deselect(): Promise<void>
Deselects the repeater item.
Promise<void>
expand(): Promise<void>
Expands the repeater item, or does nothing if already expanded.
Promise<void>
getContentText(): Promise<string>
Gets the text of the repeater item content.
Promise<string>
getTitleText(): Promise<string>
Gets the text of the repeater item title.
Promise<string>
isCollapsible(): Promise<boolean>
Whether the repeater item is collapsible.
Promise<boolean>
isExpanded(): Promise<boolean>
Whether the repeater item is expanded, or throws an error informing of the lack of collapsibility.
Promise<boolean>
isReorderable(): Promise<boolean>
Whether the repeater item is reorderable.
Promise<boolean>
isSelectable(): Promise<boolean>
Whether the repeater item is selectable.
Promise<boolean>
isSelected(): Promise<boolean>
Whether the repeater item is selected.
Promise<boolean>
queryHarness(query: HarnessQuery<T>): Promise<T>
Returns a child harness or throws an error if not found.
query: HarnessQuery<T>
Promise<T>
queryHarnesses(harness: HarnessQuery<T>): Promise<T[]>
Returns child harnesses.
harness: HarnessQuery<T>
Promise<T[]>
queryHarnessOrNull(query: HarnessQuery<T>): Promise<null | T>
Returns a child harness or null if not found.
query: HarnessQuery<T>
Promise<null | T>
querySelector(selector: string): Promise<TestElement>
Returns a child test element or throws an error if not found.
selector: string
Promise<TestElement>
querySelectorAll(selector: string): Promise<TestElement[]>
Returns child test elements.
selector: string
Promise<TestElement[]>
querySelectorOrNull(selector: string): Promise<null | TestElement>
Returns a child test element or null if not found.
selector: string
Promise<null | TestElement>
select(): Promise<void>
Selects the repeater item.
Promise<void>
sendToTop(): Promise<void>
Moves the repeater item to the top of the list
Promise<void>
SkySelectionModalSearchResultHarness.with(filters: SkyRepeaterItemHarnessFilters): HarnessPredicate<SkyRepeaterItemHarness>
Gets a HarnessPredicate
that can be used to search for a
SkyRepeaterItemHarness
that meets certain criteria.
filters: SkyRepeaterItemHarnessFilters
HarnessPredicate<SkyRepeaterItemHarness>
A set of criteria that can be used to filter a list of SkySelectionModalSearchResultHarness
instances.
interface SkySelectionModalSearchResultHarnessFilters {
contentText?: string | RegExp;
dataSkyId?: string | RegExp;
}
contentText?: string | RegExp
Only find instances whose content matches the given value.
dataSkyId?: string | RegExp
Only find instances whose data-sky-id
attribute matches the given value.
<div class="sky-margin-stacked-xl">
<button
type="button"
class="sky-btn sky-btn-default selection-modal-example-show-btn"
(click)="showSelectionModal()"
>
Select a value
</button>
</div>
@if (selectedPeople?.length) {
<div>
Selected people:
<ul class="selection-modal-example-selected">
@for (selectedPerson of selectedPeople; track selectedPerson) {
<li>
{{ selectedPerson.name }}
</li>
}
</ul>
</div>
}
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { SkySelectionModalHarness } from '@skyux/lookup/testing';
import { of } from 'rxjs';
import { LookupSelectionModalBasicExampleComponent } from './example.component';
import { ExampleService } from './example.service';
describe('Selection modal example', () => {
let mockSvc: jasmine.SpyObj<ExampleService>;
async function setupTest(): Promise<{
harness: SkySelectionModalHarness;
el: HTMLElement;
fixture: ComponentFixture<LookupSelectionModalBasicExampleComponent>;
}> {
const fixture = TestBed.createComponent(
LookupSelectionModalBasicExampleComponent,
);
const el = fixture.nativeElement as HTMLElement;
const openBtn = el.querySelector<HTMLButtonElement>(
'.selection-modal-example-show-btn',
);
openBtn?.click();
fixture.detectChanges();
const rootLoader = TestbedHarnessEnvironment.documentRootLoader(fixture);
const harness = await rootLoader.getHarness(SkySelectionModalHarness);
return { harness, el, fixture };
}
beforeEach(() => {
// Create a mock search service. In a real-world application, the search
// service would make a web request which should be avoided in unit tests.
mockSvc = jasmine.createSpyObj<ExampleService>('ExampleService', [
'search',
]);
mockSvc.search.and.callFake((searchText) => {
return of({
hasMore: false,
people:
searchText === 'ra'
? [
{
id: '1',
name: 'Rachel',
},
]
: [],
totalCount: 1,
});
});
TestBed.configureTestingModule({
imports: [
LookupSelectionModalBasicExampleComponent,
NoopAnimationsModule,
],
providers: [{ provide: ExampleService, useValue: mockSvc }],
});
});
it('should update the selected items list when an item is selected', async () => {
const { harness, el } = await setupTest();
await harness.enterSearchText('ra');
await harness.selectSearchResult({
contentText: 'Rachel',
});
await harness.saveAndClose();
const selectedItemEls = el.querySelectorAll<HTMLLIElement>(
'.selection-modal-example-selected li',
);
expect(selectedItemEls).toHaveSize(1);
expect(selectedItemEls[0].innerText.trim()).toBe('Rachel');
});
it('should not update the selected items list when the user cancels the selection modal', async () => {
const { harness, el } = await setupTest();
await harness.enterSearchText('ra');
await harness.selectSearchResult({
contentText: 'Rachel',
});
await harness.cancel();
const selectedItemEls = el.querySelectorAll<HTMLLIElement>(
'.selection-modal-example-selected li',
);
expect(selectedItemEls).toHaveSize(0);
});
it('should respect the selection descriptor', async () => {
const { harness } = await setupTest();
await expectAsync(harness.getSearchAriaLabel()).toBeResolvedTo(
'Search person',
);
await expectAsync(harness.getSaveButtonAriaLabel()).toBeResolvedTo(
'Select person',
);
});
});
import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';
import { delay } from 'rxjs/operators';
import { Person } from './person';
import { SearchResults } from './search-results';
const people: Person[] = [
{ id: '1', name: 'Abed' },
{ id: '2', name: 'Alex' },
{ id: '3', name: 'Ben' },
{ id: '4', name: 'Britta' },
{ id: '5', name: 'Buzz' },
{ id: '6', name: 'Craig' },
{ id: '7', name: 'Elroy' },
{ id: '8', name: 'Garrett' },
{ id: '9', name: 'Ian' },
{ id: '10', name: 'Jeff' },
{ id: '11', name: 'Leonard' },
{ id: '12', name: 'Neil' },
{ id: '13', name: 'Pierce' },
{ id: '14', name: 'Preston' },
{ id: '15', name: 'Rachel' },
{ id: '16', name: 'Shirley' },
{ id: '17', name: 'Todd' },
{ id: '18', name: 'Troy' },
{ id: '19', name: 'Vaughn' },
{ id: '20', name: 'Vicki' },
];
@Injectable({
providedIn: 'root',
})
export class ExampleService {
public search(searchText: string): Observable<SearchResults> {
// Simulate a network call with latency. A real-world application might
// use Angular's HttpClient to create an Observable from a call to a
// web service.
searchText = searchText.toUpperCase();
const matchingPeople = people.filter((person) =>
person.name.toUpperCase().includes(searchText),
);
return of({
hasMore: false,
people: matchingPeople,
totalCount: matchingPeople.length,
}).pipe(delay(800));
}
}
export interface Person {
id: string;
name: string;
}
import { Person } from './person';
export interface SearchResults {
hasMore: boolean;
people: Person[];
totalCount: number;
}
import { Component, inject } from '@angular/core';
import {
SkySelectionModalSearchResult,
SkySelectionModalService,
} from '@skyux/lookup';
import { map } from 'rxjs/operators';
import { ExampleService } from './example.service';
import { Person } from './person';
/**
* @title Selection modal with basic setup
*/
@Component({
standalone: true,
selector: 'app-lookup-selection-modal-basic-example',
templateUrl: './example.component.html',
})
export class LookupSelectionModalBasicExampleComponent {
protected selectedPeople: Person[] | undefined;
readonly #searchSvc = inject(ExampleService);
readonly #selectionModalSvc = inject(SkySelectionModalService);
protected showSelectionModal(): void {
const instance = this.#selectionModalSvc.open({
descriptorProperty: 'name',
idProperty: 'id',
selectionDescriptor: 'person',
searchAsync: (args) =>
this.#searchSvc.search(args.searchText).pipe(
map(
(results): SkySelectionModalSearchResult => ({
hasMore: results.hasMore,
items: results.people,
totalCount: results.totalCount,
}),
),
),
selectMode: 'single',
});
instance.closed.subscribe((args) => {
if (args.reason === 'save') {
this.selectedPeople = args.selectedItems as Person[];
}
});
}
}
<form novalidate [formGroup]="formGroup" (ngSubmit)="save()">
<sky-modal headingText="Add Item">
<sky-modal-content>
<sky-input-box labelText="Name">
<input
type="text"
autocapitalize="words"
autocomplete="off"
formControlName="name"
class="sky-form-control"
/>
</sky-input-box>
</sky-modal-content>
<sky-modal-footer>
<button class="sky-btn sky-btn-primary" type="submit">Add</button>
<button class="sky-btn sky-btn-link" type="button" (click)="close()">
Cancel
</button>
</sky-modal-footer>
</sky-modal>
</form>
import { Component, inject } from '@angular/core';
import {
FormBuilder,
FormGroup,
ReactiveFormsModule,
Validators,
} from '@angular/forms';
import { SkyInputBoxModule } from '@skyux/forms';
import { SkyModalInstance, SkyModalModule } from '@skyux/modals';
let nextId = 21;
@Component({
selector: 'app-add-item-modal',
templateUrl: './add-item-modal.component.html',
imports: [ReactiveFormsModule, SkyInputBoxModule, SkyModalModule],
})
export class AddItemModalComponent {
protected readonly formGroup: FormGroup;
readonly #modal = inject(SkyModalInstance);
constructor() {
this.formGroup = inject(FormBuilder).group({
id: [`${nextId++}`],
name: ['', Validators.required],
});
}
protected close(): void {
this.#modal.close();
}
protected save(): void {
if (this.formGroup.valid) {
this.#modal.close(this.formGroup.value, 'save');
} else {
this.formGroup.markAllAsTouched();
}
}
}
<div class="sky-margin-stacked-xl">
<button
class="sky-btn sky-btn-default selection-modal-example-show-btn"
type="button"
(click)="showSelectionModal()"
>
Select a value
</button>
</div>
@if (selectedPeople?.length) {
<div>
Selected people:
<ul class="selection-modal-example-selected">
@for (selectedPerson of selectedPeople; track selectedPerson) {
<li>
{{ selectedPerson.name }}
</li>
}
</ul>
</div>
}
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { SkySelectionModalHarness } from '@skyux/lookup/testing';
import { of } from 'rxjs';
import { LookupSelectionModalAddItemExampleComponent } from './example.component';
import { ExampleService } from './example.service';
describe('Selection modal example', () => {
let mockSvc: jasmine.SpyObj<ExampleService>;
async function setupTest(): Promise<{
harness: SkySelectionModalHarness;
el: HTMLElement;
fixture: ComponentFixture<LookupSelectionModalAddItemExampleComponent>;
}> {
const fixture = TestBed.createComponent(
LookupSelectionModalAddItemExampleComponent,
);
const el = fixture.nativeElement as HTMLElement;
const openBtn = el.querySelector<HTMLButtonElement>(
'.selection-modal-example-show-btn',
);
openBtn?.click();
fixture.detectChanges();
const rootLoader = TestbedHarnessEnvironment.documentRootLoader(fixture);
const harness = await rootLoader.getHarness(SkySelectionModalHarness);
return { harness, el, fixture };
}
beforeEach(() => {
// Create a mock search service. In a real-world application, the search
// service would make a web request which should be avoided in unit tests.
mockSvc = jasmine.createSpyObj<ExampleService>('ExampleService', [
'search',
]);
mockSvc.search.and.callFake((searchText) => {
return of({
hasMore: false,
people:
searchText === 'ra'
? [
{
id: '1',
name: 'Rachel',
},
]
: [],
totalCount: 1,
});
});
TestBed.configureTestingModule({
imports: [
LookupSelectionModalAddItemExampleComponent,
NoopAnimationsModule,
],
providers: [{ provide: ExampleService, useValue: mockSvc }],
});
});
it('should update the selected items list when an item is selected', async () => {
const { harness, el } = await setupTest();
await harness.enterSearchText('ra');
await harness.selectSearchResult({
contentText: 'Rachel',
});
await harness.saveAndClose();
const selectedItemEls = el.querySelectorAll<HTMLLIElement>(
'.selection-modal-example-selected li',
);
expect(selectedItemEls).toHaveSize(1);
expect(selectedItemEls[0].innerText.trim()).toBe('Rachel');
});
it('should not update the selected items list when the user cancels the selection modal', async () => {
const { harness, el } = await setupTest();
await harness.enterSearchText('ra');
await harness.selectSearchResult({
contentText: 'Rachel',
});
await harness.cancel();
const selectedItemEls = el.querySelectorAll<HTMLLIElement>(
'.selection-modal-example-selected li',
);
expect(selectedItemEls).toHaveSize(0);
});
it('should respect the selection descriptor', async () => {
const { harness } = await setupTest();
await expectAsync(harness.getSearchAriaLabel()).toBeResolvedTo(
'Search person',
);
await expectAsync(harness.getSaveButtonAriaLabel()).toBeResolvedTo(
'Select person',
);
});
});
import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';
import { delay } from 'rxjs/operators';
import { Person } from './person';
import { SearchResults } from './search-results';
const people: Person[] = [
{ id: '1', name: 'Abed' },
{ id: '2', name: 'Alex' },
{ id: '3', name: 'Ben' },
{ id: '4', name: 'Britta' },
{ id: '5', name: 'Buzz' },
{ id: '6', name: 'Craig' },
{ id: '7', name: 'Elroy' },
{ id: '8', name: 'Garrett' },
{ id: '9', name: 'Ian' },
{ id: '10', name: 'Jeff' },
{ id: '11', name: 'Leonard' },
{ id: '12', name: 'Neil' },
{ id: '13', name: 'Pierce' },
{ id: '14', name: 'Preston' },
{ id: '15', name: 'Rachel' },
{ id: '16', name: 'Shirley' },
{ id: '17', name: 'Todd' },
{ id: '18', name: 'Troy' },
{ id: '19', name: 'Vaughn' },
{ id: '20', name: 'Vicki' },
];
@Injectable({
providedIn: 'root',
})
export class ExampleService {
public addItem(item: Person): void {
people.push(item);
}
public search(searchText: string): Observable<SearchResults> {
// Simulate a network call with latency. A real-world application might
// use Angular's HttpClient to create an Observable from a call to a
// web service.
searchText = searchText.toUpperCase();
const matchingPeople = people.filter((person) =>
person.name.toUpperCase().includes(searchText),
);
return of({
hasMore: false,
people: matchingPeople,
totalCount: matchingPeople.length,
}).pipe(delay(800));
}
}
export interface Person {
id: string;
name: string;
}
import { Person } from './person';
export interface SearchResults {
hasMore: boolean;
people: Person[];
totalCount: number;
}
import { Component, OnDestroy, inject } from '@angular/core';
import {
SkySelectionModalAddClickEventArgs,
SkySelectionModalCloseArgs,
SkySelectionModalSearchResult,
SkySelectionModalService,
} from '@skyux/lookup';
import { SkyModalService } from '@skyux/modals';
import { Subscription } from 'rxjs';
import { map } from 'rxjs/operators';
import { AddItemModalComponent } from './add-item-modal.component';
import { ExampleService } from './example.service';
import { Person } from './person';
/**
* @title Selection modal with add item functionality
*/
@Component({
standalone: true,
selector: 'app-lookup-selection-modal-add-item-example',
templateUrl: './example.component.html',
})
export class LookupSelectionModalAddItemExampleComponent implements OnDestroy {
protected selectedPeople: Person[] | undefined;
#subscriptions = new Subscription();
readonly #modalSvc = inject(SkyModalService);
readonly #searchSvc = inject(ExampleService);
readonly #selectionModalSvc = inject(SkySelectionModalService);
public ngOnDestroy(): void {
this.#subscriptions.unsubscribe();
}
protected showSelectionModal(): void {
const instance = this.#selectionModalSvc.open({
descriptorProperty: 'name',
idProperty: 'id',
selectionDescriptor: 'person',
searchAsync: (args) =>
this.#searchSvc.search(args.searchText).pipe(
map(
(results): SkySelectionModalSearchResult => ({
hasMore: results.hasMore,
items: results.people,
totalCount: results.totalCount,
}),
),
),
selectMode: 'single',
showAddButton: true,
addClick: (args: SkySelectionModalAddClickEventArgs) => {
const modal = this.#modalSvc.open(AddItemModalComponent);
this.#subscriptions.add(
modal.closed.subscribe((close) => {
if (close.reason === 'save') {
const person = close.data as Person;
this.#searchSvc.addItem(person);
args.itemAdded({ item: person });
}
}),
);
},
});
this.#subscriptions.add(
instance.closed.subscribe((args: SkySelectionModalCloseArgs) => {
if (args.reason === 'save') {
this.selectedPeople = args.selectedItems as Person[];
}
}),
);
}
}