For input like checkbox, radio and select, provide options
to let user select data.
To give static options, provide OptionItem[]
to options.data
.
{
...
"options": {
"data": [
{
"label": "Option 1",
"value": 0
},
...
]
}
Each of the
OptionItem
usually consists of onlylabel
andvalue
. But other custom properties are also suppported.
If the options needs to be fetched from an API endpoint, then we need to provide src
. The src
accepts string
or OptionSource
. If string
is provided, it will use as the key for custom observable, see Use custom observable.
For example, the API endpoint https://dummyjson.com/products will return the following data,
{
"products": [
{
"id": 1,
"title": "iPhone 9",
"description": "An apple mobile which is nothing like apple",
"price": 549,
"discountPercentage": 12.96,
"rating": 4.69,
"stock": 94,
"brand": "Apple",
"category": "smartphones",
"thumbnail": "...",
"images": ["...", "...", "..."]
},
{...},
{...},
{...},
...
],
"total": 100,
"skip": 0,
"limit": 30
}
...and we want it to map it into OptionItem[]
like this:
[
{
"label": "iPhone 9",
"value": {
"id": 1,
"title": "iPhone 9",
"description": "An apple mobile which is nothing like apple",
"price": 549,
"discountPercentage": 12.96,
"rating": 4.69,
"stock": 94,
"brand": "Apple",
"category": "smartphones",
"thumbnail": "...",
"images": ["...", "...", "..."]
}
},
{...},
{...},
{...},
...
]
We need to set the config as below:
{
...
"options": {
"src": {
"url": "https://dummyjson.com/products",
"method": "GET",
"mapData": {
"labelKey": "title",
"contentPath": "products"
}
}
}
}
The labelKey
specify the property to use as the label
, and the contentPath
is to tell where the array of data is located. The above example shows the data we need is located at products
, so we set contentPath
to "products"
.
Use body
to pass data during HTTP request.
options.src.body
is static.options.src.trigger.body
is dynamic, its value will update when the value of the trigger control changes.{
...
"options": {
"src": {
"url": "...",
"method": "POST",
"mapData": {...},
"body": {
"brand": "...",
"category": "..."
}
}
}
}
For the method GET
, body
will transform into the parameters and merge into the URL. If the key in body
starts with :
, it acts as a variable that must match one within the url
. If not, it functions as a query string key.
Key | Value | URL |
---|---|---|
: + string |
smartphones | https://dummyjson.com/products/category/smartphones |
string |
phone | https://dummyjson.com/products/search?q=phone |
{
...
"options": {
"src": {
"url": "https://dummyjson.com/products/category/:category",
"method": "GET",
"trigger": {
"by": "productCategory",
"body": {
":category": "smartphones"
}
}
}
}
}
{
...
"options": {
"src": {
"url": "https://dummyjson.com/products/search",
"method": "GET",
"trigger": {
"by": "productCategory",
"body": {
"q": "phone"
}
}
}
}
}
If the response data is only consists of primitive value, it’s no need to provide labelKey
and valueKeys
.
[
"smartphones",
"laptops",
"fragrances",
...
]
{
...
"options": {
"src": {
"url": "https://dummyjson.com/products/categories",
"method": "GET"
}
}
}
This is a response that returns only an array of string,
mapData
can be omitted becausecontentPath
is not needed.
Option’s value can be customized by providing the valueKeys
. The value
for each of the OptionItem
will left only the properties listed inside the valueKeys
.
[
{
"label": "iPhone 9",
"value": {
"id": 1,
"price": 549,
"brand": "Apple",
"category": "smartphones",
}
},
{...},
{...},
{...},
...
]
{
...
"options": {
"src": {
"url": "https://dummyjson.com/products",
"method": "GET",
"mapData": {
"labelKey": "title",
"contentPath": "products",
"valueKeys": ["id", "price", "brand", "category"]
}
}
}
}
If the valueKeys
has only one item, then the value
will become primitive.
[
{
"label": "iPhone 9",
"value": "iPhone 9"
},
{...},
{...},
{...},
...
]
{
...
"options": {
"src": {
"url": "https://dummyjson.com/products",
"method": "GET",
"mapData": {
"labelKey": "title",
"contentPath": "products",
"valueKeys": ["title"],
}
}
}
}
The following example shows how to hoist childA and childB to the top level.
[
{
"id": 1,
"name": "...",
"propA": {
"childA": "...",
"propB": {
"childB": "..."
}
}
},
{...},
{...},
{...},
...
]
[
{
"label": "...",
"value": {
"id": 1,
"name": "...",
"childA": "...",
"childB": "..."
}
},
{...},
{...},
{...},
...
]
{
...
"options": {
"src": {
"url": "...",
"method": "...",
"mapData": {
"labelKey": "name",
"valueKeys": ["id", "name", "propA.childA", "propA.propB.childB"]
}
}
}
}
Trigger is use to make new request for the options of a control, based on the value changes from another control.
Let’s say we have a select productCategory
and another select product
. If the value of the productCategory
changes, product
will retrieve the value of productCategory
and make a http request to get all the products of that category.
[
{
...
"formControlName": "productCategory",
"options": {
"src": {
"url": "https://dummyjson.com/products/categories",
"method": "GET"
}
}
},
{
...
"formControlName": "product",
"options": {
"src": {
"url": "https://dummyjson.com/products/category/:category",
"method": "GET",
"trigger": {
"by": "productCategory",
"body": {
":category": "productCategory"
}
}
}
}
}
]
trigger.by
is the path to the control we want to listen its valueChanges
event. When productCategory
changes, its value will be get and merged into the URL.
If the productCategory
's value is "smartphones", then the result URL will be:
https://dummyjson.com/products/category/smartphones
The value of every key inside trigger.body
is the path to the value we need, which can be separated by controlPath
and valuePath
by using ,
.
{
...
"body": {
"key": "controlPath, valuePath",
...
}
}
controlPath
Path to the target control which contains value we need.valuePath
Path to the property we need, when the control's value is non-primitive.{
"controlA": {
"foo": {
"baz": "..."
},
"bar": "..."
},
"controlB": "...",
"controlC": "..."
}
{
...
"options": {
"src": {
"url": "https://dummyjson.com/products/search",
"method": "GET",
"trigger": {
"by": "controlA",
"body": {
"q": "controlA, foo.baz"
}
}
}
}
}
The example below demonstrates that when the value of the control productCategory
changes, the data is filtered by comparing the productCategory
value with the category
property.
[
{
"formControlName": "productCategory",
"options": {
"src": {
"url": "https://dummyjson.com/products/categories",
"method": "GET"
}
}
},
{
...
"options": {
"src": {
"url": "https://dummyjson.com/products",
"method": "GET",
"mapData": {
"contentPath": "products",
"labelKey": "title"
},
"filter": {
"by": "productCategory",
"conditions": {
"&&": [
["", "===", "category"]
]
}
}
}
}
}
]
The syntax of filter.conditions
is similar with the one in the Conditions. The only difference is every element’s position in the tupple must be as the following:
[0, 1, 2]
Position | Description |
---|---|
0 | Target control’s value. Use empty string if value is primitive. |
1 | The OPERATOR use to evaluate condition. |
2 | The value of each OptionItem to compare. Use empty string if value is primitive. |
conditions
support nested conditions
.
{
...
"options": {
"src": {
...
"filter": {
...
"conditions": {
"&&": [
["", "===", "category"],
{
"||": [...]
}
]
}
}
}
}
}
If the src
is a string, then it is use as a key to match with the Observable inside optionsSources
. The Observable must return OptionItem[]
.
<ng-dynamic-json-form ... [optionsSources]="optionsSources"></ng-dynamic-json-form>
optionsSources = {
custom$: this._http.get("https://dummyjson.com/products").pipe(
map((x) => (x as any).products),
concatAll(),
map((x: any) => ({ label: x.title, value: x })),
toArray()
),
};
{
...
"options": {
"src": "custom$",
...
}
}
export interface FormControlOptions {
data?: OptionItem[]:
src?: string | OptionSource;
srcAppendPosition?: 'after' | 'before';
autoSelectFirst?: boolean;
layout?: 'row' | 'column';
labelPosition?: 'before' | 'after';
containerClass?: string;
containerStyles?: string;
}
The array of options data.
Provide this to use dynamic options.
Append dynamic options after or before the static options.
Auto select the first option available on init.
The layout of the options.
The position of the label.
The class to add to the container of the options.
The styles to add to the container of the options.
export interface OptionItem {
label: string;
value?: any;
[key: string]: any;
}
export interface OptionSource {
url: string;
method: "GET" | "POST";
headers?: { [key: string]: string | string[] };
body?: { [key: string]: any };
mapData?: {
labelKey: string;
valueKeys?: string[];
contentPath?: string;
slice?: [number, number];
appendPosition?: "after" | "before";
};
trigger?: {
by: string;
body: { [key: string]: string };
debounceTime?: number;
};
filter?: {
by: string;
conditions: ConditionsGroup;
debounceTime?: number;
};
}
The url of the API endpoint.
The HTTP method use to fetch the data.
The HTTP headers to append.
The body to send over http request.
The information of how to map the data from http response to OptionItem[]
.
labelKey
The property to use as the label.valueKeys
A list of properties to map new value.contentPath
To tell where the array of data is located.slice
Slice the data after fetched.Provide to fetch data using the value of target control.
by
The path of the control to listen its valueChanges.debounceTime
The delay to add (milliseconds).body
The parameters to be used in the HTTP request.by
The path of the control to listen its valueChanges.debounceTime
The delay to add (milliseconds).conditions
The conditions use to filter out the desire data.