Dynamic Selection Providers
Dynamic selection providers let you fetch options at runtime, rather than relying on static lists.
They are useful when options need to come from a database, an API, or some logic that changes based on context.
For example, you might want to populate a dropdown with stock symbols fetched from a live exchange API, or with filtered database values.
1. Example Field Metadata
Here’s an example field configuration using a custom provider named StockApiSelectionProvider.
The selectionDynamicProviderCtxt specifies which fields from the API response should be used as labels and values.
Example Configuration
{
"name": "preferredStock",
"displayName": "Preferred Stock",
"description": "Select a stock symbol from the live exchange",
"type": "selectionDynamic",
"ormType": "varchar",
"isSystem": false,
"selectionDynamicProvider": "StockApiSelectionProvider",
"selectionDynamicProviderCtxt": "{\"labelField\": \"name\", \"valueField\": \"symbol\"}",
"selectionValueType": "string",
"required": true,
"unique": false,
"index": false,
"private": false,
"encrypt": false,
"isUserKey": false,
"enableAuditTracking": false,
"isMultiSelect": true
}
Tip
- SolidX ships with built-in providers for common use cases — see Built-in Selection Providers for details on
ListOfValuesSelectionProviderandPseudoForeignKeySelectionProvider. - Create a custom provider when the logic for fetching or filtering is more complex, or when data comes from an external source like an API.
2. Creating the Provider
Your provider class must implement the ISelectionProvider interface.
The most important method is values(), which fetches and returns the available options.
Note
The
value()method is deprecated. It can simply throw aNotImplementedExceptionand is kept only for backward compatibility.
Example: StockApiSelectionProvider
StockApiSelectionProviderimport { Injectable, Logger } from "@nestjs/common";
import { HttpService } from "@solidxai/core";
import { lastValueFrom } from "rxjs";
import { SelectionProvider } from "@solidxai/core";
import {
ISelectionProvider,
ISelectionProviderContext,
ISelectionProviderValues,
} from "../interfaces";
interface StockApiSelectionProviderContext extends ISelectionProviderContext {
labelField: string; // Field to use as label
valueField: string; // Field to use as value
}
@SelectionProvider()
@Injectable()
export class StockApiSelectionProvider
implements ISelectionProvider<StockApiSelectionProviderContext>
{
private readonly logger = new Logger(this.constructor.name);
private readonly url = "https://api.example.com/stocks"; // Example API endpoint
constructor(private readonly httpService: HttpService) {}
name(): string {
return "StockApiSelectionProvider";
}
help(): string {
return "# Fetches options dynamically from an external API.\n" +
"Context requires:\n" +
"- url: API endpoint\n" +
"- labelField: field to use for label\n" +
"- valueField: field to use for value";
}
async value(): Promise<ISelectionProviderValues | null> {
throw new Error("Not implemented (deprecated).");
}
async values(
query: string,
ctxt: StockApiSelectionProviderContext
): Promise<readonly ISelectionProviderValues[]> {
if (!ctxt.labelField || !ctxt.valueField) {
this.logger.error("Invalid context");
return [];
}
try {
const response$ = this.httpService.get(this.url);
const response = await lastValueFrom(response$);
if (!Array.isArray(response.data)) {
this.logger.warn("API response is not an array");
return [];
}
return response.data.map((item: any) => ({
label: item[ctxt.labelField],
value: item[ctxt.valueField],
}));
} catch (err) {
this.logger.error(`Failed to fetch values from API: ${err.message}`);
return [];
}
}
}
3. Registering the Provider
Since providers are standard NestJS providers, register them in the module where they should be available.
// fees-portal.module.ts
@Module({
...
providers: [StockApiSelectionProvider],
...
})
4. Interfaces
Below are the core interfaces used when implementing a dynamic selection provider.
ISelectionProvider Interface
ISelectionProvider Interfaceexport interface ISelectionProvider<T extends ISelectionProviderContext> {
// Description of the provider and expected context
help(): string;
// Unique name of the provider
name(): string;
// Deprecated method — throw NotImplementedException
value(optionValue: string, ctxt: T): Promise<ISelectionProviderValues | any>;
// Fetch selection options dynamically
values(query: any, ctxt: T): Promise<readonly ISelectionProviderValues[]>;
}
export interface ISelectionProviderContext {}
export interface ISelectionProviderValues {
label: string;
value: string;
}