@olafvv/ngx-dynamic-form 20.0.0 → 20.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +253 -239
- package/fesm2022/olafvv-ngx-dynamic-form.mjs +361 -372
- package/fesm2022/olafvv-ngx-dynamic-form.mjs.map +1 -1
- package/index.d.ts +157 -125
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,138 +1,188 @@
|
|
|
1
|
-
# NgxDynamicForm
|
|
1
|
+
# NgxDynamicForm 🚀
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://angular.io/)
|
|
4
|
+
[](https://www.npmjs.com/package/@olafvv/ngx-dynamic-form)
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
**NgxDynamicForm** is a modern, strongly-typed, and highly performant library for dynamically generating Reactive Forms based on a JSON-like configuration model.
|
|
7
|
+
|
|
8
|
+
Leveraging cutting-edge Angular features like **Strict Typed Forms** and **Signal Inputs**, this library gives you top-tier developer experience without the boilerplate. Use our polished Angular Material built-in controls out of the box, or easily plug in your own custom fields!
|
|
9
|
+
|
|
10
|
+

|
|
11
|
+
|
|
12
|
+
> _Tip: Replace the placeholder above with a GIF of the dynamic form in action!_
|
|
13
|
+
|
|
14
|
+
[](#) _(Link this to a live playground playground once deployed!)_
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## 📖 Table of contents
|
|
6
19
|
|
|
7
20
|
- [Getting Started](#getting-started)
|
|
8
|
-
- [Usage](#usage)
|
|
21
|
+
- [Basic Usage (3 Steps)](#usage)
|
|
22
|
+
- [Layout](#layout)
|
|
23
|
+
- [Field Width Control](#field-width-control)
|
|
9
24
|
- [Validators](#validators)
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
- [
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
- [Operator](#operator)
|
|
16
|
-
- [Custom Form Controls](#custom-form-controls)
|
|
25
|
+
- [Relations (Conditional Logic)](#relations)
|
|
26
|
+
- [Built-in Form Controls](#built-in-form-controls)
|
|
27
|
+
- [Creating Custom Form Controls](#custom-form-controls)
|
|
28
|
+
|
|
29
|
+
---
|
|
17
30
|
|
|
18
31
|
## Getting started
|
|
19
32
|
|
|
20
|
-
##### 1.
|
|
33
|
+
##### 1. Configure Angular Material
|
|
21
34
|
|
|
22
|
-
|
|
35
|
+
Make sure to install and configure [Angular Material](https://material.angular.io/guide/getting-started) if you want to use the built-in form controls.
|
|
23
36
|
|
|
24
|
-
|
|
37
|
+
##### 2. Install the library
|
|
38
|
+
|
|
39
|
+
```bash
|
|
25
40
|
npm i --save @olafvv/ngx-dynamic-form
|
|
26
41
|
```
|
|
27
42
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
##### 1. Import the (standalone) component
|
|
43
|
+
---
|
|
31
44
|
|
|
32
|
-
|
|
33
|
-
import { DynamicFormComponent } from 'olafvv/ngx-dynamic-form';
|
|
34
|
-
|
|
35
|
-
@NgModule({
|
|
36
|
-
//...
|
|
37
|
-
imports: [DynamicFormComponent]
|
|
38
|
-
//...
|
|
39
|
-
})
|
|
40
|
-
export class AppModule {}
|
|
41
|
-
```
|
|
45
|
+
## Usage
|
|
42
46
|
|
|
43
|
-
|
|
47
|
+
##### 1. Import the standalone component
|
|
44
48
|
|
|
45
49
|
```ts
|
|
46
50
|
import { DynamicFormComponent } from '@olafvv/ngx-dynamic-form';
|
|
47
51
|
|
|
48
52
|
@Component({
|
|
49
53
|
standalone: true,
|
|
50
|
-
imports: [DynamicFormComponent]
|
|
54
|
+
imports: [DynamicFormComponent, ReactiveFormsModule]
|
|
51
55
|
//...
|
|
52
56
|
})
|
|
53
|
-
export class AppComponent {
|
|
54
|
-
//...
|
|
55
|
-
}
|
|
57
|
+
export class AppComponent {}
|
|
56
58
|
```
|
|
57
59
|
|
|
58
60
|
##### 2. Define your form configuration
|
|
59
61
|
|
|
60
|
-
Create
|
|
62
|
+
Create an array of field models. Each model describes a single field — its type, label, validators, and any conditional logic.
|
|
61
63
|
|
|
62
64
|
```ts
|
|
63
|
-
import { DynamicInput, DynamicTextarea,
|
|
65
|
+
import { DynamicInput, DynamicTextarea, DynamicStaticText, DynamicFormConfig } from '@olafvv/ngx-dynamic-form';
|
|
64
66
|
|
|
65
67
|
export const SAMPLE_FORM: DynamicFormConfig = [
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
maxLength: 200,
|
|
85
|
-
rows: 5
|
|
86
|
-
})
|
|
87
|
-
]
|
|
68
|
+
new DynamicStaticText({
|
|
69
|
+
name: 'intro',
|
|
70
|
+
value: {
|
|
71
|
+
title: 'Contact Information',
|
|
72
|
+
text: 'Please fill in your details below.'
|
|
73
|
+
}
|
|
74
|
+
}),
|
|
75
|
+
new DynamicInput({
|
|
76
|
+
name: 'name',
|
|
77
|
+
inputType: 'text',
|
|
78
|
+
label: 'Name'
|
|
79
|
+
}),
|
|
80
|
+
new DynamicTextarea({
|
|
81
|
+
name: 'message',
|
|
82
|
+
label: 'Your message',
|
|
83
|
+
maxLength: 200,
|
|
84
|
+
rows: 5
|
|
85
|
+
})
|
|
88
86
|
];
|
|
89
87
|
```
|
|
90
88
|
|
|
91
|
-
##### 3. Create
|
|
89
|
+
##### 3. Create the Form & Render it!
|
|
92
90
|
|
|
93
|
-
|
|
91
|
+
Use the library's `DynamicFormService` to generate a strict, reactive `FormGroup` and pass tis `FormGroup` and the configuration to the `<dynamic-form>` component inside the template.
|
|
94
92
|
|
|
95
93
|
```ts
|
|
96
|
-
import {
|
|
97
|
-
import { DynamicFormService } from '@olafvv/ngx-dynamic-form;
|
|
94
|
+
import { Component, inject } from '@angular/core';
|
|
95
|
+
import { DynamicFormService, DynamicFormConfig } from '@olafvv/ngx-dynamic-form';
|
|
96
|
+
import { SAMPLE_FORM } from './sample-form';
|
|
98
97
|
|
|
99
98
|
@Component({
|
|
100
|
-
|
|
99
|
+
standalone: true,
|
|
100
|
+
imports: [DynamicFormComponent],
|
|
101
|
+
template: `
|
|
102
|
+
<form [formGroup]="formGroup">
|
|
103
|
+
<!-- 🚀 Render the dynamic form dynamically! -->
|
|
104
|
+
<dynamic-form
|
|
105
|
+
[group]="formGroup"
|
|
106
|
+
[formConfig]="formConfig" />
|
|
107
|
+
</form>
|
|
108
|
+
`
|
|
101
109
|
})
|
|
102
|
-
export class
|
|
103
|
-
|
|
104
|
-
sampleFormGroup: FormGroup<SampleFormModel> = this.dynamicFormService.createFormGroup(this.sampleFormConfig);
|
|
110
|
+
export class MyFormComponent {
|
|
111
|
+
private dynamicFormService = inject(DynamicFormService);
|
|
105
112
|
|
|
106
|
-
|
|
113
|
+
formConfig: DynamicFormConfig = SAMPLE_FORM;
|
|
114
|
+
formGroup = this.dynamicFormService.createFormGroup(this.formConfig);
|
|
107
115
|
}
|
|
108
116
|
```
|
|
109
117
|
|
|
110
|
-
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## Layout
|
|
121
|
+
|
|
122
|
+
By default, fields are stacked vertically — one per row. To arrange fields side-by-side, pass a `layout` input to `<dynamic-form>`. Each string in the array represents one **row**, containing space-separated field `name` values.
|
|
111
123
|
|
|
112
|
-
|
|
124
|
+
```ts
|
|
125
|
+
// component.ts
|
|
126
|
+
layout: string[] = [
|
|
127
|
+
'firstName lastName', // two fields side-by-side
|
|
128
|
+
'email', // full-width
|
|
129
|
+
'street city zip', // three fields in a row
|
|
130
|
+
];
|
|
131
|
+
```
|
|
113
132
|
|
|
114
133
|
```html
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
134
|
+
<!-- component.html -->
|
|
135
|
+
<dynamic-form
|
|
136
|
+
[group]="formGroup"
|
|
137
|
+
[formConfig]="formConfig"
|
|
138
|
+
[layout]="layout" />
|
|
118
139
|
```
|
|
119
140
|
|
|
120
|
-
|
|
141
|
+
Fields not referenced in the layout are not rendered. Fields within a row share available space equally by default.
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## Field Width Control
|
|
146
|
+
|
|
147
|
+
Field widths are controlled via **CSS custom properties**, keeping presentation cleanly separated from data configuration. Each field exposes a CSS variable named `--field-{name}-width`.
|
|
148
|
+
|
|
149
|
+
Since `<dynamic-form>` is rendered in your own component's template, you can set these variables from your component's scoped stylesheet — no `::ng-deep` or global styles required.
|
|
150
|
+
|
|
151
|
+
```scss
|
|
152
|
+
// my-form.component.scss
|
|
153
|
+
dynamic-form {
|
|
154
|
+
--field-postcode-width: 25%; // postcode takes 25%
|
|
155
|
+
// street fills the remaining 75% automatically
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
This approach also works natively with `@media` queries for fully responsive layouts:
|
|
160
|
+
|
|
161
|
+
```scss
|
|
162
|
+
dynamic-form {
|
|
163
|
+
--field-postcode-width: 25%;
|
|
164
|
+
|
|
165
|
+
@media (max-width: 600px) {
|
|
166
|
+
--field-postcode-width: 100%; // stack on mobile
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
---
|
|
121
172
|
|
|
122
|
-
|
|
173
|
+
## Validators
|
|
123
174
|
|
|
124
|
-
|
|
175
|
+
This library comes with a set of built-in formatters mapped seamlessly to standard [Angular Validators](https://angular.dev/api/forms/Validators). They are provided via static methods inside `DynamicFormValidators` (e.g. `DynamicFormValidators.required()`).
|
|
125
176
|
|
|
126
177
|
```ts
|
|
127
178
|
import { DynamicFormValidators } from '@olafvv/ngx-dynamic-form';
|
|
128
179
|
|
|
129
|
-
export const SAMPLE_FORM = [
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
]
|
|
180
|
+
export const SAMPLE_FORM: DynamicFormConfig = [
|
|
181
|
+
new DynamicInput({
|
|
182
|
+
name: 'email',
|
|
183
|
+
inputType: 'email',
|
|
184
|
+
validators: [DynamicFormValidators.required('Email address is required!'), DynamicFormValidators.email('Please provide a valid email')]
|
|
185
|
+
})
|
|
136
186
|
];
|
|
137
187
|
```
|
|
138
188
|
|
|
@@ -163,216 +213,180 @@ class DynamicFormValidators {
|
|
|
163
213
|
|
|
164
214
|
#### Custom Validators
|
|
165
215
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
Read more about creating custom Angular validators on the following page:
|
|
169
|
-
|
|
170
|
-
> https://blog.angular-university.io/angular-custom-validators/
|
|
171
|
-
|
|
172
|
-
E.g. a validator to require a minimum time:
|
|
216
|
+
You can easily provide custom validation logic by passing an object of type `DynamicFormValidator` into the `validators` property.
|
|
173
217
|
|
|
174
218
|
```ts
|
|
175
|
-
function minTimeValidatorFn(minTime: string): ValidatorFn {
|
|
176
|
-
return (control: AbstractControl): ValidationErrors | null => {
|
|
177
|
-
const inputTime = control.value as string;
|
|
178
|
-
|
|
179
|
-
if (!inputTime) return null;
|
|
180
|
-
|
|
181
|
-
const minTimeObj = new Date(`2000-01-01T${minTime}`);
|
|
182
|
-
const inputTimeObj = new Date(`2000-01-01T${inputTime}`);
|
|
183
|
-
|
|
184
|
-
return inputTimeObj >= minTimeObj ? null : { minTime: true };
|
|
185
|
-
};
|
|
186
|
-
}
|
|
187
|
-
|
|
188
219
|
export const minTimeValidator: (minTime: string, msg?: string) => DynamicFormValidator = (minTime: string, msg?: string) => ({
|
|
189
220
|
name: 'minTime',
|
|
190
|
-
validator: minTimeValidatorFn(minTime),
|
|
191
|
-
message: msg ?? `
|
|
221
|
+
validator: minTimeValidatorFn(minTime), // Your custom function returning an Angular ValidationErrors object
|
|
222
|
+
message: msg ?? `Minimum time allowed is ${minTime}`
|
|
192
223
|
});
|
|
193
224
|
```
|
|
194
225
|
|
|
195
|
-
|
|
196
|
-
import { minTimeValidator } from './min-time-validator.ts';
|
|
197
|
-
|
|
198
|
-
//..
|
|
199
|
-
|
|
200
|
-
const formConfig: DynamicFormConfig = [
|
|
201
|
-
[
|
|
202
|
-
new DynamicInput({
|
|
203
|
-
name: 'time',
|
|
204
|
-
inputType: 'time',
|
|
205
|
-
validators: [minTimeValidator('11:00')]
|
|
206
|
-
})
|
|
207
|
-
]
|
|
208
|
-
];
|
|
209
|
-
```
|
|
226
|
+
---
|
|
210
227
|
|
|
211
228
|
## Relations
|
|
212
229
|
|
|
213
|
-
Sometimes you want to create
|
|
230
|
+
Sometimes you want to create interconnected logic between fields (e.g., hiding a passport number input unless the document type is set to "Passport"). NgxDynamicForm handles conditionally reactive states natively using **Relations**.
|
|
231
|
+
|
|
232
|
+
Each relation defines an Action Type, a source condition, and an operator.
|
|
214
233
|
|
|
215
|
-
|
|
234
|
+
##### DynamicFormFieldRelation type
|
|
216
235
|
|
|
217
236
|
```ts
|
|
218
|
-
|
|
237
|
+
type DynamicFormFieldRelation {
|
|
219
238
|
actionType: RelationActionType;
|
|
220
239
|
conditions: RelationCondition[];
|
|
221
240
|
operator?: RelationOperator;
|
|
222
241
|
}
|
|
223
242
|
```
|
|
224
243
|
|
|
225
|
-
##### Action
|
|
226
|
-
|
|
227
|
-
The action type is the type of state the field should be when the conditions are met. Right now the library contains three different action types:
|
|
228
|
-
|
|
229
|
-
- Disable/Enable
|
|
230
|
-
- Visible/Hidden
|
|
231
|
-
- Required/Optional
|
|
232
|
-
|
|
233
|
-
To set the type you have to use one of the provided enums:
|
|
234
|
-
|
|
235
|
-
```ts
|
|
236
|
-
enum RelationActionType {
|
|
237
|
-
DISABLED = 'DISABLED',
|
|
238
|
-
ENABLED = 'ENABLED',
|
|
239
|
-
HIDDEN = 'HIDDEN',
|
|
240
|
-
VISIBLE = 'VISIBLE',
|
|
241
|
-
REQUIRED = 'REQUIRED',
|
|
242
|
-
OPTIONAL = 'OPTIONAL'
|
|
243
|
-
}
|
|
244
|
-
```
|
|
245
|
-
|
|
246
|
-
##### Conditions
|
|
247
|
-
|
|
248
|
-
The conditions is an array of `RelationCondition` objects. Each condition contains the name of the field the related field is depended on and a value when the condition is met:
|
|
249
|
-
|
|
250
|
-
```ts
|
|
251
|
-
interface RelationCondition {
|
|
252
|
-
// The name of the field this field is related to
|
|
253
|
-
fieldName: string;
|
|
254
|
-
// A method which has to return a boolean. Returning true means the condition is met
|
|
255
|
-
value: (val: any) => boolean;
|
|
256
|
-
}
|
|
257
|
-
```
|
|
258
|
-
|
|
259
|
-
##### Operator
|
|
260
|
-
|
|
261
|
-
The operator is an optional property and determines if all or any one of the conditions have to return true to trigger the required state of the field. By default the value of this property is `RelationOperator.AND` and is only used when there are more than 1 conditions.
|
|
262
|
-
|
|
263
|
-
```ts
|
|
264
|
-
enum RelationOperator {
|
|
265
|
-
// All conditions have to equal true
|
|
266
|
-
AND = 'AND',
|
|
267
|
-
// On of the conditions have to equal true
|
|
268
|
-
OR = 'OR'
|
|
269
|
-
}
|
|
270
|
-
```
|
|
244
|
+
##### Action Types
|
|
271
245
|
|
|
272
|
-
|
|
246
|
+
- `DISABLED` / `ENABLED`
|
|
247
|
+
- `HIDDEN` / `VISIBLE`
|
|
248
|
+
- `REQUIRED` / `OPTIONAL`
|
|
273
249
|
|
|
274
|
-
|
|
250
|
+
##### Example: Conditional Visibility
|
|
275
251
|
|
|
276
252
|
```ts
|
|
277
|
-
const formConfig = [
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
]
|
|
302
|
-
})
|
|
303
|
-
]
|
|
253
|
+
const formConfig: DynamicFormConfig = [
|
|
254
|
+
new DynamicSelect({
|
|
255
|
+
name: 'documentType',
|
|
256
|
+
label: 'Document type',
|
|
257
|
+
options: [
|
|
258
|
+
{ label: 'Passport', value: 'passport' },
|
|
259
|
+
{ label: 'ID Card', value: 'id' }
|
|
260
|
+
]
|
|
261
|
+
}),
|
|
262
|
+
new DynamicInput({
|
|
263
|
+
name: 'passportNumber',
|
|
264
|
+
label: 'Passport number',
|
|
265
|
+
relations: [
|
|
266
|
+
{
|
|
267
|
+
actionType: RelationActionType.VISIBLE, // Make this field visible...
|
|
268
|
+
conditions: [
|
|
269
|
+
{
|
|
270
|
+
fieldName: 'documentType', // ...when 'documentType' field...
|
|
271
|
+
value: (val: string) => val === 'passport' // ...equals 'passport'
|
|
272
|
+
}
|
|
273
|
+
]
|
|
274
|
+
}
|
|
275
|
+
]
|
|
276
|
+
})
|
|
304
277
|
];
|
|
305
278
|
```
|
|
306
279
|
|
|
307
|
-
|
|
280
|
+
---
|
|
308
281
|
|
|
309
|
-
|
|
282
|
+
## Built-in form controls
|
|
310
283
|
|
|
311
|
-
The library comes with
|
|
284
|
+
The library comes with a battle-tested set of built-in form controls utilizing **Angular Material**.
|
|
312
285
|
|
|
313
|
-
|
|
|
314
|
-
| :---------------- |
|
|
315
|
-
| Button
|
|
316
|
-
| Button toggle
|
|
317
|
-
| Checkbox
|
|
318
|
-
|
|
|
319
|
-
|
|
|
320
|
-
|
|
|
321
|
-
|
|
|
322
|
-
|
|
|
286
|
+
| Control Name | Description |
|
|
287
|
+
| :---------------- | :------------------------------------------------------------------------------------ |
|
|
288
|
+
| **Button** | Highly-customizable actionable button with a click callback. |
|
|
289
|
+
| **Button toggle** | Horizontal toggle groupings ideal for single or multi-select radio behavior. |
|
|
290
|
+
| **Checkbox** | Standard binary state checkbox. |
|
|
291
|
+
| **Slide toggle** | Standard binary state slide toggle. |
|
|
292
|
+
| **Input** | Standard HTML5 inputs with embedded floating labels, validation hints, and matchings. |
|
|
293
|
+
| **Radio group** | Vertically or horizontally stacked radio selectors. |
|
|
294
|
+
| **Readonly** | Presentational un-editable field representation. |
|
|
295
|
+
| **Static Text** | Presentational text for titles and/or descriptions. |
|
|
296
|
+
| **Select** | Dropdown menu powered by `mat-select` or native `<select>`. |
|
|
297
|
+
| **Textarea** | Auto-resizing text area input. |
|
|
323
298
|
|
|
324
|
-
|
|
299
|
+
---
|
|
325
300
|
|
|
326
301
|
## Custom Form Controls
|
|
327
302
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
> https://blog.angular-university.io/angular-custom-form-controls/
|
|
331
|
-
|
|
332
|
-
After that follow the following steps.
|
|
333
|
-
|
|
334
|
-
#### 1. Create a model
|
|
303
|
+
NgxDynamicForm was built with modern extensibility in mind. Creating a brand new dynamic control is easy using the generic `DynamicFormFieldBase<M>` abstraction.
|
|
335
304
|
|
|
336
|
-
|
|
337
|
-
Also, you need to create an (unique) name for the type of the model/.
|
|
305
|
+
### 1. Create a Model & Options Type
|
|
338
306
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
##### Interface:
|
|
342
|
-
|
|
343
|
-
The interface extends the base interface `DynamicFormFieldConfig` and expects a generic type describing the possible value(s) of the field. In this case that would be a number or null value.
|
|
307
|
+
First, define a type for your specific options and a model class that Angular will parse.
|
|
344
308
|
|
|
345
309
|
```typescript
|
|
346
|
-
|
|
347
|
-
min: number;
|
|
348
|
-
max: number;
|
|
349
|
-
step: number;
|
|
350
|
-
}
|
|
351
|
-
```
|
|
310
|
+
import { DynamicFormFieldValueConfig, DynamicFormFieldValueModel } from '@olafvv/ngx-dynamic-form';
|
|
352
311
|
|
|
353
|
-
|
|
312
|
+
export type SliderInputConfig = DynamicFormFieldValueConfig<number | null> & {
|
|
313
|
+
min?: number;
|
|
314
|
+
max?: number;
|
|
315
|
+
step?: number;
|
|
316
|
+
};
|
|
354
317
|
|
|
355
|
-
The model is what is called when creating the form config (e.g. `new SliderInput(sliderConfig)`).
|
|
356
|
-
The model contains the same properties defined in the configuration interface, and provides them with a value from the config or a default value.
|
|
357
|
-
|
|
358
|
-
Also, you need to create a field type token which we can later use to map the used model to the control component. In this case we created the token with the name `DYNAMIC_FORM_FIELD_SLIDER` with the value `slider`. The value HAS to be unique.
|
|
359
|
-
|
|
360
|
-
```typescript
|
|
361
318
|
export const DYNAMIC_FORM_FIELD_SLIDER = 'slider';
|
|
362
319
|
|
|
363
320
|
export class SliderInput extends DynamicFormFieldValueModel<number | null> {
|
|
364
321
|
public min: number;
|
|
365
322
|
public max: number;
|
|
366
323
|
public step: number;
|
|
367
|
-
|
|
368
|
-
readonly type = DYNAMIC_FORM_FIELD_SLIDER;
|
|
324
|
+
public readonly type = DYNAMIC_FORM_FIELD_SLIDER;
|
|
369
325
|
|
|
370
326
|
constructor(config: SliderInputConfig) {
|
|
371
327
|
super(config);
|
|
372
|
-
|
|
373
328
|
this.min = config.min ?? 0;
|
|
374
329
|
this.max = config.max ?? 10;
|
|
375
330
|
this.step = config.step ?? 1;
|
|
376
331
|
}
|
|
377
332
|
}
|
|
378
333
|
```
|
|
334
|
+
|
|
335
|
+
### 2. Create the Strongly-Typed Component
|
|
336
|
+
|
|
337
|
+
Because this library uses **Angular 17+ Signal Inputs**, your custom component should extend `DynamicFormFieldBase` natively.
|
|
338
|
+
|
|
339
|
+
**`slider.component.ts`**:
|
|
340
|
+
|
|
341
|
+
```typescript
|
|
342
|
+
import { Component, input } from '@angular/core';
|
|
343
|
+
import { FormGroup, ReactiveFormsModule } from '@angular/forms';
|
|
344
|
+
import { DynamicFormFieldBase } from '@olafvv/ngx-dynamic-form';
|
|
345
|
+
import { SliderInput } from './slider-input.model';
|
|
346
|
+
|
|
347
|
+
@Component({
|
|
348
|
+
standalone: true,
|
|
349
|
+
selector: 'app-custom-slider',
|
|
350
|
+
imports: [ReactiveFormsModule],
|
|
351
|
+
template: `
|
|
352
|
+
<div
|
|
353
|
+
class="slider-wrapper"
|
|
354
|
+
[formGroup]="group()">
|
|
355
|
+
<label>{{ model().label }}</label>
|
|
356
|
+
|
|
357
|
+
<!-- Your custom markup here -->
|
|
358
|
+
<input
|
|
359
|
+
type="range"
|
|
360
|
+
[min]="model().min"
|
|
361
|
+
[max]="model().max"
|
|
362
|
+
[step]="model().step"
|
|
363
|
+
[formControlName]="model().name" />
|
|
364
|
+
</div>
|
|
365
|
+
`
|
|
366
|
+
})
|
|
367
|
+
// Notice how passing `SliderInput` strictly types the generic base class!
|
|
368
|
+
export class SliderComponent extends DynamicFormFieldBase<SliderInput> {
|
|
369
|
+
public model = input.required<SliderInput>();
|
|
370
|
+
public group = input.required<FormGroup>();
|
|
371
|
+
}
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
### 3. Registering the Control
|
|
375
|
+
|
|
376
|
+
Finally, tell the `DynamicFormService` how to connect the `SliderInput` model (`slider`) to the `SliderComponent` rendering engine via dependency injection using `DYNAMIC_FORM_CONTROL_MAP`.
|
|
377
|
+
|
|
378
|
+
```ts
|
|
379
|
+
@NgModule({
|
|
380
|
+
...
|
|
381
|
+
providers: [
|
|
382
|
+
{
|
|
383
|
+
provide: DYNAMIC_FORM_FIELD_MAP,
|
|
384
|
+
useValue: {
|
|
385
|
+
[DYNAMIC_FORM_FIELD_SLIDER]: SliderComponent
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
]
|
|
389
|
+
})
|
|
390
|
+
export class AppModule {}
|
|
391
|
+
|
|
392
|
+
```
|