tuain-ng-forms-lib 17.3.6 → 17.4.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 +280 -335
- package/esm2022/lib/classes/forms/action.mjs +3 -3
- package/esm2022/lib/classes/forms/element.mjs +2 -2
- package/esm2022/lib/classes/forms/field.mjs +4 -7
- package/esm2022/lib/classes/forms/form.mjs +4 -6
- package/esm2022/lib/classes/forms/piece-propagate.mjs +1 -1
- package/esm2022/lib/classes/forms/piece.mjs +2 -2
- package/esm2022/lib/classes/forms/section.mjs +2 -2
- package/esm2022/lib/classes/forms/subsection.mjs +1 -1
- package/esm2022/lib/classes/forms/table/action.mjs +1 -1
- package/esm2022/lib/classes/forms/table/column.mjs +1 -1
- package/esm2022/lib/classes/forms/table/row-data.mjs +3 -5
- package/esm2022/lib/classes/forms/table/table.mjs +2 -2
- package/esm2022/lib/components/elements/layout/piece.component.mjs +3 -3
- package/esm2022/lib/components/elements/tables/table-record-action.component.mjs +1 -1
- package/esm2022/lib/components/elements/tables/table-record-field.component.mjs +1 -1
- package/esm2022/lib/components/forms/basic-form.mjs +23 -24
- package/esm2022/lib/interfaces/action.interface.mjs +2 -0
- package/esm2022/lib/interfaces/field.interface.mjs +2 -0
- package/esm2022/lib/interfaces/form-config.interface.mjs +1 -1
- package/esm2022/lib/interfaces/form.interface.mjs +2 -0
- package/esm2022/lib/interfaces/index.mjs +9 -1
- package/esm2022/lib/interfaces/piece.interface.mjs +2 -0
- package/esm2022/lib/interfaces/section.interface.mjs +2 -0
- package/esm2022/lib/interfaces/sse-live-connection.interface.mjs +2 -0
- package/esm2022/lib/interfaces/table.interface.mjs +2 -0
- package/esm2022/lib/services/file-manager.service.mjs +5 -5
- package/esm2022/lib/services/form-manager.service.mjs +5 -5
- package/esm2022/lib/services/icon-dictionary.service.mjs +1 -1
- package/esm2022/lib/services/sse-live-connection.service.mjs +165 -0
- package/esm2022/lib/tokens/sse-live-connection.token.mjs +7 -0
- package/esm2022/public-api.mjs +3 -1
- package/fesm2022/tuain-ng-forms-lib.mjs +215 -55
- package/fesm2022/tuain-ng-forms-lib.mjs.map +1 -1
- package/lib/classes/forms/action.d.ts +3 -2
- package/lib/classes/forms/element.d.ts +2 -1
- package/lib/classes/forms/field.d.ts +5 -4
- package/lib/classes/forms/form.d.ts +4 -2
- package/lib/classes/forms/piece-propagate.d.ts +2 -1
- package/lib/classes/forms/piece.d.ts +4 -3
- package/lib/classes/forms/section.d.ts +3 -2
- package/lib/classes/forms/subsection.d.ts +2 -1
- package/lib/classes/forms/table/action.d.ts +2 -1
- package/lib/classes/forms/table/column.d.ts +2 -1
- package/lib/classes/forms/table/row-data.d.ts +3 -2
- package/lib/classes/forms/table/table.d.ts +3 -1
- package/lib/components/elements/layout/piece.component.d.ts +2 -2
- package/lib/components/forms/basic-form.d.ts +4 -4
- package/lib/interfaces/action.interface.d.ts +23 -0
- package/lib/interfaces/field.interface.d.ts +70 -0
- package/lib/interfaces/form-config.interface.d.ts +24 -16
- package/lib/interfaces/form.interface.d.ts +106 -0
- package/lib/interfaces/index.d.ts +7 -0
- package/lib/interfaces/piece.interface.d.ts +67 -0
- package/lib/interfaces/section.interface.d.ts +56 -0
- package/lib/interfaces/sse-live-connection.interface.d.ts +59 -0
- package/lib/interfaces/table.interface.d.ts +139 -0
- package/lib/services/file-manager.service.d.ts +4 -4
- package/lib/services/form-manager.service.d.ts +3 -3
- package/lib/services/icon-dictionary.service.d.ts +1 -1
- package/lib/services/sse-live-connection.service.d.ts +48 -0
- package/lib/tokens/sse-live-connection.token.d.ts +7 -0
- package/package.json +2 -1
- package/public-api.d.ts +2 -0
package/README.md
CHANGED
|
@@ -1,411 +1,356 @@
|
|
|
1
|
+
# tuain-ng-forms-lib
|
|
1
2
|
|
|
2
|
-
|
|
3
|
+
UI-agnostic core of the Tuain platform's **dynamic forms** framework for Angular 17+.
|
|
3
4
|
|
|
4
|
-
|
|
5
|
+
It lets you build form-driven applications by rendering complete forms —fields, actions, tables and sections— from **JSON definitions sent by the server**, using a **reactive** programming model: each form is a class that declares *how it reacts* to UI events through callbacks, while the library takes care of instantiating the model, keeping the view in sync and orchestrating backend communication.
|
|
5
6
|
|
|
6
|
-
|
|
7
|
+
> This library provides the **domain model**, the **abstract base components** and the **services**. It does not impose a UI library: concrete widgets are supplied by UI packages (e.g. `tuain-ng-forms-ant` with ng-zorro, `tuain-ng-forms-ionic` with Ionic) or by the application itself, by extending the base components.
|
|
7
8
|
|
|
8
|
-
|
|
9
|
+
## Table of contents
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
1. [Purpose and architecture](#1-purpose-and-architecture)
|
|
12
|
+
2. [Services](#2-services)
|
|
13
|
+
3. [Forms API and the reactive approach](#3-forms-api-and-the-reactive-approach)
|
|
14
|
+
4. [Form lifecycle](#4-form-lifecycle)
|
|
15
|
+
5. [Form composition and element access](#5-form-composition-and-element-access)
|
|
16
|
+
6. [Model objects vs. view components](#6-model-objects-vs-view-components)
|
|
17
|
+
7. [Using the library in an Angular project](#7-using-the-library-in-an-angular-project)
|
|
13
18
|
|
|
14
|
-
|
|
19
|
+
---
|
|
15
20
|
|
|
16
|
-
|
|
21
|
+
## 1. Purpose and architecture
|
|
17
22
|
|
|
18
|
-
|
|
19
|
-
import { TuainNgFormsLibModule } from 'tuain-ng-forms-lib';
|
|
20
|
-
|
|
21
|
-
...
|
|
22
|
-
@NgModule({
|
|
23
|
-
imports: [
|
|
24
|
-
...
|
|
25
|
-
TuainNgFormsLibModule,
|
|
26
|
-
...,
|
|
27
|
-
],
|
|
28
|
-
declarations: [
|
|
29
|
-
...
|
|
30
|
-
],
|
|
31
|
-
exports: [
|
|
32
|
-
...
|
|
33
|
-
TuainNgFormsLibModule,
|
|
34
|
-
...
|
|
35
|
-
],
|
|
36
|
-
})
|
|
37
|
-
```
|
|
23
|
+
The goal is to **leverage the construction of form-based applications**: instead of hand-coding every screen, the server describes the form (fields, actions, tables, sections, states) as JSON and the library materializes it as a graph of live objects with reactive behavior. The application only writes the form's **business logic** (what to do when a field is validated, an action is executed, etc.).
|
|
38
24
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
Tuain-ng-forms-lib expose a series of components and services to provide base clases to build forms in
|
|
42
|
-
Angular applications following the Tuain Platforms standard behavior. The elements exposed by the API
|
|
43
|
-
are described below.
|
|
25
|
+
The architecture is organized in layers:
|
|
44
26
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
27
|
+
```
|
|
28
|
+
┌──────────────────────────────────────────────────────────────────┐
|
|
29
|
+
│ PRESENTATION LAYER (concrete UI) │
|
|
30
|
+
│ tuain-ng-forms-ant (ng-zorro) · tuain-ng-forms-ionic (Ionic)·App │
|
|
31
|
+
│ Widgets that extend the base components and supply the template │
|
|
32
|
+
├──────────────────────────────────────────────────────────────────┤
|
|
33
|
+
│ BASE COMPONENTS LAYER (abstract, standalone) │
|
|
34
|
+
│ FieldComponent · ActionComponent · LibTableComponent · … │
|
|
35
|
+
│ Sync model→view via signals; translate UI events │
|
|
36
|
+
├──────────────────────────────────────────────────────────────────┤
|
|
37
|
+
│ MODEL LAYER (domain) │
|
|
38
|
+
│ FormStructureAndData · FieldDescriptor · FormAction · RecordTable │
|
|
39
|
+
│ Form structure + data + state (live objects) │
|
|
40
|
+
├──────────────────────────────────────────────────────────────────┤
|
|
41
|
+
│ SERVICES LAYER │
|
|
42
|
+
│ FormManager · EventManager · FileManagement · IconResolver · SSE │
|
|
43
|
+
│ Server definition/actions, navigation, events, files │
|
|
44
|
+
└──────────────────────────────────────────────────────────────────┘
|
|
45
|
+
│
|
|
46
|
+
▼
|
|
47
|
+
Angular 17+ · RxJS
|
|
48
|
+
```
|
|
64
49
|
|
|
65
|
-
|
|
50
|
+
Model hierarchy (each level adds capabilities):
|
|
66
51
|
|
|
67
|
-
|
|
68
|
-
|
|
52
|
+
```
|
|
53
|
+
FormPiece visibility, enablement, states, customAttributes
|
|
54
|
+
└─ FormPiecePropagate reactive attribute propagation (BehaviorSubject)
|
|
55
|
+
├─ FormElement elementType + setAttr() with propagation
|
|
56
|
+
│ ├─ FieldDescriptor field: value, validation, options, errors
|
|
57
|
+
│ ├─ FormAction action: backend, inProgress, restrictions
|
|
58
|
+
│ └─ RecordTable table: columns, records, paging, filters
|
|
59
|
+
├─ RecordFormSection section (activation/navigation)
|
|
60
|
+
└─ RecordFormSubSection subsection (groups elements)
|
|
61
|
+
|
|
62
|
+
FormStructureAndData form container (fields + actions + tables + state)
|
|
63
|
+
└─ BasicFormComponent Angular component: lifecycle + callbacks + server
|
|
64
|
+
```
|
|
69
65
|
|
|
70
|
-
|
|
66
|
+
Every public entity also has a **contract interface** (`IForm`, `IField`, `IAction`, `ITable`, `ITableColumn`, `ITableAction`, `ISection`, …) implemented by the classes; applications may type against the contracts instead of the concrete classes.
|
|
71
67
|
|
|
72
|
-
|
|
73
|
-
present in the form definition.
|
|
68
|
+
---
|
|
74
69
|
|
|
75
|
-
|
|
70
|
+
## 2. Services
|
|
76
71
|
|
|
77
|
-
|
|
72
|
+
Services decouple the core from each application's specifics. They are provided via DI; some are **abstract by design** (the app implements them).
|
|
78
73
|
|
|
79
|
-
|
|
|
80
|
-
| --- |
|
|
81
|
-
|
|
|
82
|
-
|
|
|
83
|
-
|
|
|
84
|
-
|
|
|
85
|
-
|
|
|
86
|
-
| showLabel | Define if the action shows it's label
|
|
74
|
+
| Service / Token | Role | App implements? |
|
|
75
|
+
| --- | --- | --- |
|
|
76
|
+
| `LibFormManagerService` | Fetches the form definition (`getFormDefinition`), runs server actions (`execServerAction`), navigates between forms and manages the **navigation stack** (`openForm`, `backTo`, `stack`/`unstack`). | Yes (overrides the server/navigation methods) |
|
|
77
|
+
| `LibEventManagerService` | The app's **event bus** (Subject / BehaviorSubject / ReplaySubject) for communication between forms and components. | Instantiated with its set of events |
|
|
78
|
+
| `LibFileManagementService` | File handling (`openFile`, `saveFile`, `saveFileFromURL`, `printPdfFile`) per platform (web/mobile). | Yes |
|
|
79
|
+
| `BaseIconResolverService` + `ICON_RESOLVER` | **Extensible, multi-collection** icon resolution (each UI registers its collections). | Optional (extend/register) |
|
|
80
|
+
| `SseLiveConnectionService` + `SSE_LIVE_CONNECTION_CONFIG` | **Server→client** event channel over Server-Sent Events (native reconnection). All integration is provided through the config token. | Provides the config |
|
|
87
81
|
|
|
88
|
-
|
|
82
|
+
The core never assumes a concrete implementation: it defines the contract and the app satisfies it via `providers`.
|
|
89
83
|
|
|
90
|
-
|
|
84
|
+
---
|
|
91
85
|
|
|
92
|
-
|
|
93
|
-
* icon: Returns the icon name of the action
|
|
94
|
-
* isVisible: Informs if the action is visible in the form
|
|
86
|
+
## 3. Forms API and the reactive approach
|
|
95
87
|
|
|
96
|
-
|
|
88
|
+
An application form is a **class that extends `BasicFormComponent`**. The central idea of the reactive approach is that **you don't program the flow imperatively**; instead you *declare how the form reacts to UI events*, registering callbacks that the library invokes at the right moment.
|
|
97
89
|
|
|
98
|
-
|
|
90
|
+
UI components don't call business logic directly: they translate the UI event into a notification on the **model object**, and `BasicFormComponent` —subscribed to that object— fires the callbacks the app registered.
|
|
99
91
|
|
|
100
|
-
|
|
92
|
+
```
|
|
93
|
+
User types in the input
|
|
94
|
+
│ (template) (ngModelChange)
|
|
95
|
+
▼
|
|
96
|
+
FieldComponent.inputChanged()
|
|
97
|
+
├─ updateObject() → field.setValue(...) (view → model)
|
|
98
|
+
└─ onChangeContent() → field.notifyEditionFinish()
|
|
99
|
+
│ emits on editionFinish (Subject)
|
|
100
|
+
▼
|
|
101
|
+
BasicFormComponent.startFieldValidation(code)
|
|
102
|
+
│
|
|
103
|
+
▼
|
|
104
|
+
callbacks registered with onFieldValidationStart(...)
|
|
105
|
+
│ (if ok and field.backend)
|
|
106
|
+
▼
|
|
107
|
+
requestFormAction('VALIDATE') → updateFormWithServerData()
|
|
108
|
+
▼
|
|
109
|
+
callbacks registered with onFieldValidationFinish(...)
|
|
110
|
+
```
|
|
101
111
|
|
|
102
|
-
|
|
103
|
-
| --- | ----------- |
|
|
104
|
-
| fieldObject | |
|
|
105
|
-
| currentMode | |
|
|
112
|
+
Conversely, when the model changes (by code or by a server response) the view updates itself thanks to **reactive propagation**:
|
|
106
113
|
|
|
107
|
-
|
|
114
|
+
```
|
|
115
|
+
field.setValue(x) → setAttr() → propagateAttribute('value', x)
|
|
116
|
+
│ │ _attributeChange.next(...) (BehaviorSubject)
|
|
117
|
+
│ ▼
|
|
118
|
+
│ FieldComponent (subscribed to field.attributeChange)
|
|
119
|
+
│ ▼
|
|
120
|
+
│ this.value.set(x) (signal) → template
|
|
121
|
+
```
|
|
108
122
|
|
|
109
|
-
|
|
123
|
+
### Registering callbacks (how the form "reacts")
|
|
110
124
|
|
|
111
|
-
|
|
112
|
-
* onInputChange: Allows to link the change on the native element with the form manager behavior
|
|
113
|
-
* onChangeContent: Allows to link the typing on the native element with the form manager behavior
|
|
114
|
-
* onShowInfo: Allows to link the show infor request on the component custom template with the
|
|
115
|
-
form event handler defined to support the respective action request.
|
|
116
|
-
* numberInputValidation: Allows to use the number input validation to restrict the typing
|
|
125
|
+
Inside `start()` (or `preStart()`) the app registers the handlers:
|
|
117
126
|
|
|
118
|
-
|
|
127
|
+
```typescript
|
|
128
|
+
// Actions
|
|
129
|
+
this.onActionStart('save', (action) => this.validateBeforeSaving()); // false cancels
|
|
130
|
+
this.onActionFinish('save', (action, result) => this.notifySaved());
|
|
119
131
|
|
|
120
|
-
|
|
132
|
+
// Fields
|
|
133
|
+
this.onFieldValidationStart('email', (field) => this.preValidateEmail(field));
|
|
134
|
+
this.onFieldValidationFinish('email', (field) => this.afterValidateEmail(field));
|
|
135
|
+
this.onFieldInput('phone', (field) => this.formatWhileTyping(field));
|
|
121
136
|
|
|
122
|
-
|
|
137
|
+
// Tables
|
|
138
|
+
this.onTableActionStart('items', 'edit', (detail) => this.openEdition(detail));
|
|
139
|
+
this.onTableGetDataFinish('items', (detail, result) => this.afterPaging(detail));
|
|
123
140
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
| formManager | |
|
|
141
|
+
// Sections and form
|
|
142
|
+
this.onSectionActivation('data', (section) => this.onEnterSection(section));
|
|
143
|
+
this.onFormChange((state) => this.onStateChanged(state));
|
|
128
144
|
|
|
129
|
-
|
|
145
|
+
// Server errors
|
|
146
|
+
this.onActionServerError((action) => this.showError());
|
|
147
|
+
```
|
|
130
148
|
|
|
131
|
-
|
|
149
|
+
Every callback that runs **before** the backend (`...Start`) can return `false` to **cancel** the flow. The `...Finish` ones run after the server part.
|
|
132
150
|
|
|
133
|
-
|
|
151
|
+
---
|
|
134
152
|
|
|
135
|
-
|
|
136
|
-
| --- | ----------- |
|
|
137
|
-
| errorTitle | |
|
|
138
|
-
| errorMessage | |
|
|
139
|
-
| errorType | |
|
|
153
|
+
## 4. Form lifecycle
|
|
140
154
|
|
|
141
|
-
|
|
155
|
+
```
|
|
156
|
+
1. Angular creates the form component (a BasicFormComponent subclass)
|
|
157
|
+
2. ngOnInit() ─────────────► preStart() (config hook: formCode, route)
|
|
158
|
+
3. The app calls formInit(params):
|
|
159
|
+
a. processInputParams() extracts token / subject / state from the stack
|
|
160
|
+
b. getFormDefinition(name) requests the JSON from the server (FormManager)
|
|
161
|
+
c. loadDefinition(json) ──────────► instantiates FieldDescriptor / FormAction /
|
|
162
|
+
RecordTable / RecordFormSection (live model)
|
|
163
|
+
d. subscribeSectionActivation()
|
|
164
|
+
subscribeFieldsSubjects() the library subscribes to each model object's
|
|
165
|
+
subscribeActionSubjects() events and connects its state to the form's
|
|
166
|
+
subscribeTableSubjects() (connectWithParentForm)
|
|
167
|
+
e. changeState(initialState) state machine (visibility/editability)
|
|
168
|
+
f. requestFormAction('GETDATA') initial data load (if loadInitialData)
|
|
169
|
+
g. start() app hook: callbacks are registered HERE
|
|
170
|
+
4. Operation: the UI fires events → notifications on the model → callbacks
|
|
171
|
+
5. Angular destroys the component → takeUntilDestroyed cleans up the subscriptions
|
|
172
|
+
```
|
|
142
173
|
|
|
143
|
-
|
|
174
|
+
Key points:
|
|
144
175
|
|
|
145
|
-
|
|
176
|
+
- **`preStart()`**: configure `name` (the form code) and route parameters.
|
|
177
|
+
- **`start()`**: register the callbacks (`on*`). Runs once the model is already loaded.
|
|
178
|
+
- **`changeState(state)`**: the **state machine** defines, for each state, which fields/actions/tables are visible and editable (`visibleStates` / `enabledStates`). Changing state re-propagates visibility and enablement to the whole view.
|
|
179
|
+
- **`requestFormAction(code)`**: the single point of backend communication; its response enters through `updateFormWithServerData()` and updates the existing fields/actions/tables via `updateFromServer()`.
|
|
146
180
|
|
|
147
|
-
|
|
148
|
-
| --- | ----------- |
|
|
149
|
-
| formManager | |
|
|
150
|
-
| icon | |
|
|
151
|
-
| goBackAction | |
|
|
152
|
-
| showTitle | |
|
|
153
|
-
| headerActions | |
|
|
181
|
+
---
|
|
154
182
|
|
|
155
|
-
|
|
183
|
+
## 5. Form composition and element access
|
|
156
184
|
|
|
157
|
-
|
|
185
|
+
`FormStructureAndData` (which `BasicFormComponent` extends) holds the form graph and exposes a **rich API** to access and manipulate its elements. Each element is obtained by its `code` and operated directly on the model object.
|
|
158
186
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
187
|
+
```typescript
|
|
188
|
+
// Fields → FieldDescriptor (IField)
|
|
189
|
+
const email = this.getField('email');
|
|
190
|
+
email.setValue('a@b.com');
|
|
191
|
+
email.required = true;
|
|
192
|
+
if (email.hasError()) { /* … */ }
|
|
193
|
+
this.getFieldValue('email'); // shortcut
|
|
194
|
+
this.getRequiredEmptyFields(null, 'data'); // by section
|
|
195
|
+
|
|
196
|
+
// Actions → FormAction (IAction)
|
|
197
|
+
const save = this.getAction('save');
|
|
198
|
+
this.disableAction('save');
|
|
199
|
+
this.getHeaderActions();
|
|
200
|
+
|
|
201
|
+
// Tables → RecordTable (ITable), with columns and table actions
|
|
202
|
+
const items = this.getTable('items');
|
|
203
|
+
items.setTableRecords(records);
|
|
204
|
+
items.getActions('INLINE'); // ITableAction[]
|
|
205
|
+
items.columns; // ITableColumn[]
|
|
206
|
+
const rec = this.getTableRecord('items', id);// ITableRecord
|
|
207
|
+
|
|
208
|
+
// Sections → RecordFormSection (ISection) / RecordFormSubSection (ISubSection)
|
|
209
|
+
this.activateSection('data');
|
|
210
|
+
this.getSubSection('data', 'personal');
|
|
211
|
+
```
|
|
162
212
|
|
|
163
|
-
|
|
213
|
+
Element ↔ component relationship:
|
|
164
214
|
|
|
165
|
-
|
|
215
|
+
| Element (model) | Contract | Base view component |
|
|
216
|
+
| --- | --- | --- |
|
|
217
|
+
| `FieldDescriptor` | `IField` | `FieldComponent` |
|
|
218
|
+
| `FormAction` | `IAction` | `ActionComponent` |
|
|
219
|
+
| `RecordTable` | `ITable` | `LibTableComponent` |
|
|
220
|
+
| `RecordTableColumn` / `TableAction` | `ITableColumn` / `ITableAction` | cells in `LibTableRecordFieldComponent` / `LibTableRecordActionComponent` |
|
|
221
|
+
| `RecordFormSection` / `RecordFormSubSection` | `ISection` / `ISubSection` | `SectionComponent` / `SubSectionComponent` |
|
|
222
|
+
| `FormStructureAndData` | `IForm` | `BasicFormComponent` / `FormHeaderComponent` |
|
|
166
223
|
|
|
167
|
-
|
|
224
|
+
Each component receives its model object via `@Input()` (`[field]`, `[action]`, `[table]`, …), registers itself as that object's *widget* and **subscribes to its changes** to reflect them; and it translates user events into notifications on that same object. In other words, **the component is a view of the model object, not its owner**.
|
|
168
225
|
|
|
169
|
-
|
|
226
|
+
---
|
|
170
227
|
|
|
171
|
-
|
|
228
|
+
## 6. Model objects vs. view components
|
|
172
229
|
|
|
173
|
-
|
|
230
|
+
This is the most important distinction for using the library correctly:
|
|
174
231
|
|
|
175
|
-
|
|
232
|
+
```
|
|
233
|
+
MODEL (lives for the whole form) VIEW (ephemeral, created/destroyed by Angular)
|
|
234
|
+
┌───────────────────────────┐ ┌───────────────────────────┐
|
|
235
|
+
│ FieldDescriptor 'email' │◄── @Input ────│ FieldComponent │
|
|
236
|
+
│ (created in loadDefinition│ field.widget │ (created when rendered) │
|
|
237
|
+
│ lives until the form is │◄── subscription│ signals: value(), … │
|
|
238
|
+
│ closed) │ attributeChange destroyed when hidden │
|
|
239
|
+
└───────────────────────────┘ └───────────────────────────┘
|
|
240
|
+
▲ source of truth ▲ recreated without losing data
|
|
241
|
+
│ getField('email') always returns the same object
|
|
242
|
+
```
|
|
176
243
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
| sectionObject | |
|
|
180
|
-
| formManager | |
|
|
244
|
+
- **Model objects** (`FieldDescriptor`, `FormAction`, `RecordTable`, …) are created **once** in `loadDefinition()` and **live for as long as the form exists**. They hold the state: value, errors, visibility, selection, etc. `getField('x')` **always returns the same instance**.
|
|
245
|
+
- **Components** (`FieldComponent`, etc.) are **ephemeral**: Angular creates and destroys them based on what is currently shown (section change, `@if`, table paging, virtual scroll…). When destroyed and recreated, **nothing is lost**: the new component re-subscribes to the model object and recovers the current state.
|
|
181
246
|
|
|
182
|
-
|
|
247
|
+
**Practical consequences:**
|
|
183
248
|
|
|
184
|
-
|
|
249
|
+
- The form's business logic operates **on the model objects** (`this.getField('x').setValue(...)`), never on the components. It works even if the component is not mounted.
|
|
250
|
+
- Don't keep state in the component expecting it to persist; state lives in the model.
|
|
251
|
+
- A model object and its component **are not the same thing**: the component is a replaceable reactive projection of the object, whereas the object is the stable, unique entity.
|
|
185
252
|
|
|
186
|
-
|
|
253
|
+
---
|
|
187
254
|
|
|
188
|
-
|
|
189
|
-
| --- | ----------- |
|
|
190
|
-
| subSection | |
|
|
191
|
-
| showHeader | |
|
|
192
|
-
| formManager | |
|
|
255
|
+
## 7. Using the library in an Angular project
|
|
193
256
|
|
|
194
|
-
|
|
257
|
+
### Installation
|
|
195
258
|
|
|
196
|
-
|
|
259
|
+
```bash
|
|
260
|
+
npm install tuain-ng-forms-lib
|
|
261
|
+
# or: yarn add tuain-ng-forms-lib
|
|
262
|
+
```
|
|
197
263
|
|
|
198
|
-
|
|
264
|
+
Peer dependencies: Angular `^17` (`common`, `core`, `forms`, `router`) and `rxjs ^7.5`.
|
|
199
265
|
|
|
200
|
-
|
|
201
|
-
| --- | ----------- |
|
|
202
|
-
| table | |
|
|
203
|
-
| visiblePages | |
|
|
204
|
-
| pagesGroup | |
|
|
266
|
+
### a. Provide the services
|
|
205
267
|
|
|
206
|
-
|
|
268
|
+
Standalone apps register them in `main.ts` (or in an `NgModule` with `providers`):
|
|
207
269
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
270
|
+
```typescript
|
|
271
|
+
import { bootstrapApplication } from '@angular/platform-browser';
|
|
272
|
+
import { LibFormManagerService, LibFileManagementService, LibEventManagerService } from 'tuain-ng-forms-lib';
|
|
273
|
+
import { AppFormManager } from './services/app-form-manager.service'; // your implementation
|
|
274
|
+
import { AppFileManager } from './services/app-file-manager.service';
|
|
275
|
+
|
|
276
|
+
bootstrapApplication(AppComponent, {
|
|
277
|
+
providers: [
|
|
278
|
+
{ provide: LibFormManagerService, useClass: AppFormManager },
|
|
279
|
+
{ provide: LibFileManagementService, useClass: AppFileManager },
|
|
280
|
+
{ provide: LibEventManagerService, useValue: new LibEventManagerService(['formActivity', /* … */]) },
|
|
281
|
+
// optional: ICON_RESOLVER, SSE_LIVE_CONNECTION_CONFIG
|
|
282
|
+
],
|
|
283
|
+
});
|
|
284
|
+
```
|
|
211
285
|
|
|
212
|
-
|
|
286
|
+
`AppFormManager` extends `LibFormManagerService` and implements the bridge to your backend:
|
|
213
287
|
|
|
214
|
-
|
|
288
|
+
```typescript
|
|
289
|
+
@Injectable()
|
|
290
|
+
export class AppFormManager extends LibFormManagerService {
|
|
291
|
+
override async getFormDefinition(formCode: string): Promise<any> { /* HTTP → JSON */ }
|
|
292
|
+
override async execServerAction(detail: any): Promise<any> { /* HTTP → response */ }
|
|
293
|
+
override goToForm(formCode: string, token: string, subject: string | null): void { /* router */ }
|
|
294
|
+
}
|
|
295
|
+
```
|
|
215
296
|
|
|
216
|
-
|
|
297
|
+
### b. Define a form
|
|
217
298
|
|
|
218
|
-
|
|
299
|
+
```typescript
|
|
300
|
+
import { Component } from '@angular/core';
|
|
301
|
+
import { BasicFormComponent, LibFormManagerService, LibEventManagerService, LibFileManagementService } from 'tuain-ng-forms-lib';
|
|
302
|
+
import { appFormConfig } from './app-form.config'; // your IFormConfig
|
|
303
|
+
|
|
304
|
+
@Component({ standalone: true, selector: 'app-customer-form', templateUrl: './customer-form.html' })
|
|
305
|
+
export class CustomerFormComponent extends BasicFormComponent {
|
|
306
|
+
constructor(fm: LibFormManagerService, em: LibEventManagerService, fs: LibFileManagementService) {
|
|
307
|
+
super(fm, em, fs);
|
|
308
|
+
this.setConfig(appFormConfig);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
override preStart(): void {
|
|
312
|
+
this.name = 'CUSTOMER'; // form code
|
|
313
|
+
this.formInit({ /* route params */ }); // triggers load + initialization
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
override start(): void {
|
|
317
|
+
// Declare HOW the form reacts:
|
|
318
|
+
this.onFieldValidationStart('documentId', (f) => this.validateDocument(f));
|
|
319
|
+
this.onActionStart('save', () => this.validateSectionConsistency('data'));
|
|
320
|
+
this.onActionFinish('save', () => this.goBack());
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
private validateDocument(field /* IField */): boolean {
|
|
324
|
+
if (field.empty) { field.setErrorMessage('Required'); return false; }
|
|
325
|
+
return true;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
```
|
|
219
329
|
|
|
220
|
-
|
|
330
|
+
### c. Render
|
|
221
331
|
|
|
222
|
-
|
|
332
|
+
The core ships **abstract base components**: to show concrete widgets you use a Tuain UI package (e.g. `tuain-ng-forms-ant` / `tuain-ng-forms-ionic`) or your own components that **extend** `FieldComponent`, `ActionComponent`, `LibTableComponent`, etc. and supply their template. In the form template you bind each component to its model object:
|
|
223
333
|
|
|
224
|
-
|
|
334
|
+
```html
|
|
335
|
+
<lib-form-header [form]="form" (goBackEvent)="goBack()"></lib-form-header>
|
|
225
336
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
| recordData | |
|
|
337
|
+
<app-field *ngFor="let f of getFields()" [field]="f"></app-field>
|
|
338
|
+
<app-action *ngFor="let a of getHeaderActions()" [action]="a"></app-action>
|
|
339
|
+
<app-table *ngFor="let t of getTables()" [table]="t"></app-table>
|
|
340
|
+
```
|
|
231
341
|
|
|
232
|
-
|
|
342
|
+
> Until the UI packages are available, the app defines its own field/action/table components by extending this library's base components and overriding `start()`/`focus()` and the template.
|
|
233
343
|
|
|
234
|
-
|
|
344
|
+
---
|
|
235
345
|
|
|
236
|
-
|
|
346
|
+
## Public API
|
|
237
347
|
|
|
238
|
-
|
|
239
|
-
| --- | ----------- |
|
|
240
|
-
| table | |
|
|
241
|
-
| tableRecords | |
|
|
242
|
-
| currentMode | |
|
|
243
|
-
| waiting | |
|
|
348
|
+
Exported (from `tuain-ng-forms-lib`):
|
|
244
349
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
| Output Event | Description |
|
|
250
|
-
| --- | ----------- |
|
|
251
|
-
| tableActionActivated | |
|
|
252
|
-
| tableStartGetData | |
|
|
253
|
-
|
|
254
|
-
#### BasicFormComponent
|
|
255
|
-
|
|
256
|
-
##### Methods
|
|
257
|
-
|
|
258
|
-
This component is provides as the base class to define any form in the Angular application as a super-class
|
|
259
|
-
to provide all basic form operation with a full set of methods to allow make any objects access and
|
|
260
|
-
modifications.
|
|
261
|
-
|
|
262
|
-
##### General form operation methods
|
|
263
|
-
|
|
264
|
-
This set of methods are oriented to access and modify general form attributes and configuration.
|
|
265
|
-
|
|
266
|
-
| Method Name | Description |
|
|
267
|
-
| --- | ----------- |
|
|
268
|
-
| `supportState` | Informs if a form supports the operation on a particular state |
|
|
269
|
-
| `supportMode`<br>*deprecated* | Informs if a form supports the operation on a particular state|
|
|
270
|
-
| `getTitle` | Returns de form title |
|
|
271
|
-
| `cleanData` | Cleand all fields and tables |
|
|
272
|
-
| `displayActionServerError` | Show the error after the execution of the backend portion of an action|
|
|
273
|
-
| `displayValidationServerError` | Show the error after the execution of the backend portion of a field validation |
|
|
274
|
-
| `showModalDialog` | Shows a customized modal dialog|
|
|
275
|
-
| `openUploadDialog` | Shows a standar file upload modal dialog|
|
|
276
|
-
| `goBackForm` | Change the route of the application to go back to previous form|
|
|
277
|
-
| `goToSubPage` | Request a new route to be displayed |
|
|
278
|
-
| `setError` | Define the global form error|
|
|
279
|
-
| `get formManager` | Returns the object that supports the data and structure of the form|
|
|
280
|
-
| `resetError` | Clear the global error form object|
|
|
281
|
-
| `getErrorType` | Returns the global form error type |
|
|
282
|
-
| `getErrorMessage`<br>*deprecated* | Returns the global form error message |
|
|
283
|
-
| `getErrorDetail`<br>*deprecated* | Returns the global form error detail |
|
|
284
|
-
| `getErrorCode` | | Returns the global form error code |
|
|
285
|
-
| `get errorMessage` | Returns the global form error message |
|
|
286
|
-
| `get errorDetail` | Returns the global form error detail |
|
|
287
|
-
| `getCurrentState` | Return the current state |
|
|
288
|
-
| `getCurrentMode`<br>*deprecated* | Return the current state |
|
|
289
|
-
| `getSubject` | Return the subject id of the form |
|
|
290
|
-
| `getformSubject`<br>*deprecated* | Return the subject id of the form |
|
|
291
|
-
| `errorOccured`| Return if there is an error |
|
|
292
|
-
| `changeState` | Modify the current state |
|
|
293
|
-
|
|
294
|
-
###### Fields related methods
|
|
295
|
-
|
|
296
|
-
This set of methods allow subclass to access and modify fields in the form.
|
|
297
|
-
|
|
298
|
-
| Method Name | Description |
|
|
299
|
-
| --- | ----------- |
|
|
300
|
-
| `getFields` | Returns the form fields |
|
|
301
|
-
| `getField` | Returns a form field object based on its name|
|
|
302
|
-
| `enableField` | Set an status attribute of the field in order to be editable|
|
|
303
|
-
| `disableField` | Set an status attribute of the field in order prevent edition |
|
|
304
|
-
| `getFieldValue` | Returns the value of a field |
|
|
305
|
-
| `getFieldOptions` | Returns an array with all posible values of the field with a description text|
|
|
306
|
-
| `setFieldValue` | Change the current value of a field |
|
|
307
|
-
| `setFieldErrorMessage` | Defines an error message for a field |
|
|
308
|
-
| `setFieldOptions` | Define the posible values of the field with an array of values and description text |
|
|
309
|
-
| `setFieldRequired` | Change an status attribute of the field to determine if it is required |
|
|
310
|
-
| `applyProcessToFieldSet` | Execute a process on all the fields specified in an array, section or sub-section. If the fields are not specified, the execition will be over the whole set of fields in the form `(processFunc, fieldArray?, sectionCode?, subSectionCode?)`|
|
|
311
|
-
| `cleanFields` | Clean all the fields specified in an array, section or sub-section using their default value. If the fields are not specified, the execition will be over the whole set of fields in the form `(fieldArray?, sectionCode?, subSectionCode?)`|
|
|
312
|
-
| `getRequiredFields` | Obtain all the required fields specified in an array, section or sub-section using their default value. If the fields are not specified, the execition will be over the whole set of fields in the form `(fieldArray?, sectionCode?, subSectionCode?)`|
|
|
313
|
-
| `getRequiredEmptyFields` | Obtain all the required and empty fields specified in an array, section or sub-section using their default value. If the fields are not specified, the execition will be over the whole set of fields in the form `(fieldArray?, sectionCode?, subSectionCode?)`|
|
|
314
|
-
| `getChangedFields` | Obtain all the fields that changed since the last backend refresh over a field set specified in an array, section or sub-section using their default value. If the fields are not specified, the execition will be over the whole set of fields in the form `(fieldArray?, sectionCode?, subSectionCode?)`|
|
|
315
|
-
| `getFieldsWithValidationIssues` | Obtain all the fields that has validation errors over a field set specified in an array, section or sub-section using their default value. If the fields are not specified, the execition will be over the whole set of fields in the form `(fieldArray?, sectionCode?, subSectionCode?)`|
|
|
316
|
-
| `tagFieldsWithError` | Set an error mesagge for all the fields specified in an array, section or sub-section using their default value. If the fields are not specified, the execition will be over the whole set of fields in the form `(errorMessage, fieldArray?, sectionCode?, subSectionCode?)`|
|
|
317
|
-
| `cleanErrorFields` | Clean the error on all the fields specified in an array, section or sub-section using their default value. If the fields are not specified, the execition will be over the whole set of fields in the form `(fieldArray?, sectionCode?, subSectionCode?)`|
|
|
318
|
-
| `showLabelFields` | Show the label on all the fields specified in an array, section or sub-section using their default value. If the fields are not specified, the execition will be over the whole set of fields in the form `(fieldArray?, sectionCode?, subSectionCode?)`|
|
|
319
|
-
| `hideLabelFields` | Hide the label on all the fields specified in an array, section or sub-section using their default value. If the fields are not specified, the execition will be over the whole set of fields in the form `(fieldArray?, sectionCode?, subSectionCode?)`|
|
|
320
|
-
| `enableFields` | Enable for edition all the fields specified in an array, section or sub-section using their default value. If the fields are not specified, the execition will be over the whole set of fields in the form `(fieldArray?, sectionCode?, subSectionCode?)`|
|
|
321
|
-
| `disableFields` | Disable for edition all the fields specified in an array, section or sub-section using their default value. If the fields are not specified, the execition will be over the whole set of fields in the form `(fieldArray?, sectionCode?, subSectionCode?)`|
|
|
322
|
-
| `showFields` | Show all the fields specified in an array, section or sub-section using their default value. If the fields are not specified, the execition will be over the whole set of fields in the form `(fieldArray?, sectionCode?, subSectionCode?)`|
|
|
323
|
-
| `hideFields` | Hide all the fields specified in an array, section or sub-section using their default value. If the fields are not specified, the execition will be over the whole set of fields in the form `(fieldArray?, sectionCode?, subSectionCode?)`|
|
|
324
|
-
| `showFieldInfo` | Show help information related with a field |
|
|
325
|
-
| `addFieldInputValidation` | `(fields, callbackMethod)`|
|
|
326
|
-
| `addFieldValidationStart` | Defines a callback function to be triggered when a field change its content in order to be validated. The returned value of this callback defines if the process must continue in the backend|
|
|
327
|
-
| `addFieldValidationFinish` | Defines a callback function to be triggered when the field validation process returns from the backend portion execution in order to be completed |
|
|
328
|
-
| `startFieldInputValidation` | Defines a callback function to be triggered when a field receive any input and is change is not complete, so the user typing can be verified and validated. This process do not have any backend process by default
|
|
329
|
-
| `startFieldValidation` | Start the field validation process|
|
|
330
|
-
| `startServerFieldValidation` | Trigger the execution of a field validation, starting on the backend portion without the first callbacks execution|
|
|
331
|
-
|
|
332
|
-
###### Actions related methods
|
|
333
|
-
|
|
334
|
-
This set of methods allow subclass to access and modify actions in the form.
|
|
335
|
-
|
|
336
|
-
| Method Name | Description |
|
|
337
|
-
| --- | ----------- |
|
|
338
|
-
| `getHeaderActions` | Returns the form actions defined to be placed on the form header|
|
|
339
|
-
| `getAction` | Returns a form action object based on its name|
|
|
340
|
-
| `showAction` | Change the visibility of an action to be displayed based on its name|
|
|
341
|
-
| `hideAction` | Change the visibility of an action to be hidden based on its name|
|
|
342
|
-
| `showActions` | Change the visibility of an action set to be displayed based on an array of names|
|
|
343
|
-
| `hideActions` | Change the visibility of an action set to be hidden based on an array of names|
|
|
344
|
-
| `enableAction` | Modify the status of an action to be available for execution|
|
|
345
|
-
| `disableAction` | Modify the status of an action to prevent execution|
|
|
346
|
-
| `enableActions` | Modify the status of an actionset to be available for execution based on an array of names|
|
|
347
|
-
| `disableActions` | Modify the status of an action to prevent execution based on an array of names|
|
|
348
|
-
| `addActionMethodStart` | Defines a callback function to be triggered when an action is requested. The returned value of this callback defines if the process must continue in the backend|
|
|
349
|
-
| `addActionMethodFinish` | Defines a callback function to be triggered when an action returns from the backend portion execution in order to be completed |
|
|
350
|
-
| `startAction` | Start the execution of an action, starting with the initial callbacks, continuing in the backend if former callbacks were ok and execuing the after backend callbacks|
|
|
351
|
-
| `startServerAction` | Trigger the execution of an action, starting on the backend portion without the first callbacks execution|
|
|
352
|
-
|
|
353
|
-
###### Tables related methods
|
|
354
|
-
|
|
355
|
-
This set of methods allow subclass to access and modify tables in the form.
|
|
356
|
-
|
|
357
|
-
| Method Name | Description |
|
|
358
|
-
| --- | ----------- |
|
|
359
|
-
| `getTables` | Returns all form tables |
|
|
360
|
-
| `getTable` | Returns a form table object based on its name |
|
|
361
|
-
| `showTable` | Change the visibility of a table to be displayed based on its name |
|
|
362
|
-
| `hideTable` | Change the visibility of a table to be hidden based on its name|
|
|
363
|
-
| `showTables` | Change the visibility of a table set to be displayed based on an array of names|
|
|
364
|
-
| `hideTables` | Change the visibility of a table set to be hidden based on an array of names|
|
|
365
|
-
| `cleanTable` | Delete the content of a table |
|
|
366
|
-
| `getTableRecord` | Returns an `array` with all records of a table|
|
|
367
|
-
| `displayTableServerError` | Shows an error message with the result of a table action on the backend|
|
|
368
|
-
| `addTableActionStart` | Defines a callback function to be triggered when an action of the table is requested. The returned value of this callback defines if the process must continue in the backend|
|
|
369
|
-
| `addTableActionFinish` | Defines a callback function to be triggered when an action of the table returns from the backend portion execution in order to be completed |
|
|
370
|
-
| `addTableGetDataStart` | Defines a callback function to be triggered when the table need to refresh or update its data as a consequence of pagination or any refresh request|
|
|
371
|
-
| `addTableGetDataFinish` | Defines a callback function to be triggered when the table data refresh or update is executed on the backend|
|
|
372
|
-
| `startTableAction` | Start the execution of a table action, starting with the callback actions, continue in the backend if former callbacks were ok and execute the after backend callbacks|
|
|
373
|
-
| `startTableServerAction` | Trigger the execution of a table action, starting on the backend portion without the first callbacks execution|
|
|
374
|
-
| `startTableGetData` | Start the execution of a table get data action, starting with the callback actions, continue in the backend if former callbacks were ok and execute the after backend callbacks|
|
|
375
|
-
| `startTableServerGetData` | Trigger the execution of a table get data action, starting on the backend portion without the first callbacks execution|
|
|
376
|
-
|
|
377
|
-
###### Sections related methods
|
|
378
|
-
|
|
379
|
-
This set of methods allow subclass to access and modify fields in the form.
|
|
380
|
-
|
|
381
|
-
| Method Name | Description |
|
|
382
|
-
| --- | ----------- |
|
|
383
|
-
| `getSections` | Returns all form sections |
|
|
384
|
-
| `getSection` | Returns the section object based on its name |
|
|
385
|
-
| `activateSection` | Set a section as the active one based on its name |
|
|
386
|
-
| `activeSection` | Returns the name of the active section |
|
|
387
|
-
| `getSubSection` | Returns the sub-ection object based on the section name and subsection name |
|
|
388
|
-
| `getSectionsTitles` | Returns an array with all section titles |
|
|
389
|
-
| `showSection` | Change the visibility of a section to be displayed based on its name |
|
|
390
|
-
| `hideSection` | Change the visibility of a section to be hidden based on its name |
|
|
391
|
-
| `showSections` | Change the visibility of a section set to be displayed based on an array of names|
|
|
392
|
-
| `hideSections` | Change the visibility of a section set to be hidden based on an array of names|
|
|
393
|
-
| `showSubSection` | Change the visibility of a sub-section to be displayed based on the section name and subsection name|
|
|
394
|
-
| `hideSubSection` | Change the visibility of a sub-section to be hidden based on the section name and subsection name|
|
|
395
|
-
| `addSectionActivation` | Defines a callback function to be triggered when any of the sections with name in the input array of section names is activated|
|
|
396
|
-
| `addSectionInactivation` | Defines a callback function to be triggered when any of the sections with name in the input array of section names is inactivated|
|
|
397
|
-
| `checkSectionRequiredFields` | Return a list of a section based on its name, with the required fields that are empty |
|
|
398
|
-
| `validateSectionConsistency` | Verify that all required fields have value and don't have any validation errors |
|
|
399
|
-
|
|
400
|
-
###### Utilities and complex methods
|
|
401
|
-
|
|
402
|
-
This set of methods interact with multiple element types to provide tools to simplify complex
|
|
403
|
-
but common operations in the forms.
|
|
404
|
-
|
|
405
|
-
| Method Name | Description |
|
|
406
|
-
| --- | ----------- |
|
|
407
|
-
| `copyTableRecordToFields` | Copy all columns in a table record to a set of fields using a mapping objec to relate column names with field names. If the mapping object is not present, look for fields with same names|
|
|
408
|
-
| `defineEditionTable` | Configure an object to copy fields, enable edition, hide fields and actions to operate table and fields for records edition|
|
|
350
|
+
- **Model:** `FormStructureAndData`, `FieldDescriptor`, `FormAction`, `RecordTable`, `RecordTableColumn`, `TableAction`, `TableRecordData`, `RecordFormSection`, `RecordFormSubSection`, and the bases `FormPiece`/`FormPiecePropagate`/`FormElement`.
|
|
351
|
+
- **Contracts:** `IForm`, `IField`, `IAction`, `ITable`, `ITableColumn`, `ITableAction`, `ITableRecord`, `ISection`, `ISubSection`, `IFormPiece`/`IFormElement`, `IFormConfig` and the definition/event types.
|
|
352
|
+
- **Base components (standalone):** `BasicFormComponent`, `FieldComponent`, `ActionComponent`, `LibTableComponent`, `LibTableRecordFieldComponent`, `LibTableRecordActionComponent`, `SectionComponent`, `SubSectionComponent`, `FormHeaderComponent`, `FormErrorComponent`, `ElementComponent`, `PieceComponent`.
|
|
353
|
+
- **Services and tokens:** `LibFormManagerService`, `LibEventManagerService`, `LibFileManagementService`, `BaseIconResolverService` + `ICON_RESOLVER`, `SseLiveConnectionService` + `SSE_LIVE_CONNECTION_CONFIG`.
|
|
409
354
|
|
|
410
355
|
## License
|
|
411
356
|
|