@suntelecoms/ngx-dynamic-form 1.1.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 +950 -0
- package/fesm2022/suntelecoms-ngx-dynamic-form.mjs +1100 -0
- package/fesm2022/suntelecoms-ngx-dynamic-form.mjs.map +1 -0
- package/index.d.ts +5 -0
- package/lib/components/dynamic-field/dynamic-field.component.d.ts +21 -0
- package/lib/components/dynamic-form/dynamic-form.component.d.ts +34 -0
- package/lib/components/form-builder/form-builder.component.d.ts +69 -0
- package/lib/components/form-loader/form-loader.component.d.ts +24 -0
- package/lib/components/mes-formulaires/mes-formulaires.component.d.ts +26 -0
- package/lib/models/form-field.model.d.ts +68 -0
- package/lib/models/saved-form.model.d.ts +7 -0
- package/lib/providers.d.ts +11 -0
- package/lib/services/condition-evaluator.service.d.ts +12 -0
- package/lib/services/dynamic-form-builder.service.d.ts +19 -0
- package/lib/services/form-config.service.d.ts +16 -0
- package/lib/storage/form-storage.adapter.d.ts +9 -0
- package/lib/storage/http-storage.adapter.d.ts +16 -0
- package/lib/storage/local-storage.adapter.d.ts +14 -0
- package/package.json +37 -0
- package/public-api.d.ts +15 -0
package/README.md
ADDED
|
@@ -0,0 +1,950 @@
|
|
|
1
|
+
# @suntelecoms/ngx-dynamic-form
|
|
2
|
+
|
|
3
|
+
> Génère des formulaires Angular complexes à partir d'une configuration TypeScript — sans écrire une ligne de template HTML. Inclut un Form Builder visuel, la gestion des schémas sauvegardés et un système de stockage interchangeable (localStorage / API REST).
|
|
4
|
+
|
|
5
|
+
[](https://angular.dev)
|
|
6
|
+
[](https://material.angular.io)
|
|
7
|
+
[](CHANGELOG.md)
|
|
8
|
+
[](LICENSE)
|
|
9
|
+
[](mailto:amadoumacbookpro38@gmail.com)
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Sommaire
|
|
14
|
+
|
|
15
|
+
- [Présentation](#présentation)
|
|
16
|
+
- [Fonctionnalités](#fonctionnalités)
|
|
17
|
+
- [Installation](#installation)
|
|
18
|
+
- [Démarrage rapide — formulaire statique](#démarrage-rapide--formulaire-statique)
|
|
19
|
+
- [Démarrage rapide — stack complète](#démarrage-rapide--stack-complète)
|
|
20
|
+
- [Types de champs](#types-de-champs)
|
|
21
|
+
- [Configuration — DynamicFormConfig](#configuration--dynamicformconfig)
|
|
22
|
+
- [Propriétés d'un champ — DynamicFormField](#propriétés-dun-champ--dynamicformfield)
|
|
23
|
+
- [Validation](#validation)
|
|
24
|
+
- [Affichage conditionnel — showWhen](#affichage-conditionnel--showwhen)
|
|
25
|
+
- [Icônes (Material Icons)](#icônes-material-icons)
|
|
26
|
+
- [Disposition en grille (col)](#disposition-en-grille-col)
|
|
27
|
+
- [API du composant DynamicForm](#api-du-composant-dynamicform)
|
|
28
|
+
- [provideNgxForms — configuration globale](#providengxforms--configuration-globale)
|
|
29
|
+
- [FormConfigService — gestion des schémas](#formconfigservice--gestion-des-schémas)
|
|
30
|
+
- [Stockage — LocalStorage vs API REST](#stockage--localstorage-vs-api-rest)
|
|
31
|
+
- [Form Builder — éditeur visuel](#form-builder--éditeur-visuel)
|
|
32
|
+
- [Composants de navigation](#composants-de-navigation)
|
|
33
|
+
- [Intégration dans un composant](#intégration-dans-un-composant)
|
|
34
|
+
- [Pages de démonstration](#pages-de-démonstration)
|
|
35
|
+
- [Exemples complets](#exemples-complets)
|
|
36
|
+
- [Personnalisation CSS](#personnalisation-css)
|
|
37
|
+
- [Interfaces TypeScript](#interfaces-typescript)
|
|
38
|
+
- [Auteur](#auteur)
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Présentation
|
|
43
|
+
|
|
44
|
+
**@suntelecoms/ngx-dynamic-form** est un plugin Angular 19 qui génère des formulaires réactifs dynamiquement à partir d'un objet de configuration TypeScript. Il s'appuie sur **Angular Reactive Forms** et **Angular Material**.
|
|
45
|
+
|
|
46
|
+
Depuis la **v1.1.0**, le plugin embarque directement :
|
|
47
|
+
- Le **Form Builder visuel** (`<ngx-form-builder>`)
|
|
48
|
+
- Le **gestionnaire de formulaires** (`<ngx-mes-formulaires>`)
|
|
49
|
+
- La **page de rendu par ID** (`<ngx-form-loader>`)
|
|
50
|
+
- Un **système de stockage abstrait** (localStorage par défaut, API REST Spring en option)
|
|
51
|
+
|
|
52
|
+
Résultat : dans n'importe quel projet Angular, il suffit de **2 lignes** pour avoir une interface complète de paramétrage de formulaires.
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Fonctionnalités
|
|
57
|
+
|
|
58
|
+
- **16 types de champs** — `text`, `email`, `password`, `number`, `tel`, `url`, `date`, `textarea`, `select`, `multiselect`, `radio`, `checkbox`, `file`, `hidden`, `divider`, `heading`
|
|
59
|
+
- **Floating label** Angular Material (`mat-form-field appearance="outline"`)
|
|
60
|
+
- **Validation déclarative** — `required`, `email`, `minLength`, `maxLength`, `min`, `max`, `pattern`, validateur personnalisé
|
|
61
|
+
- **Messages d'erreur personnalisés** par champ
|
|
62
|
+
- **Affichage conditionnel** (`showWhen`) avec 7 opérateurs
|
|
63
|
+
- **Conditions multiples** (logique AND)
|
|
64
|
+
- **Grille 12 colonnes** — propriété `col`
|
|
65
|
+
- **Icônes Material Icons** — préfixe et suffixe par champ
|
|
66
|
+
- **Préremplissage** via `[initialValues]`
|
|
67
|
+
- **Mode lecture seule / désactivé** par champ
|
|
68
|
+
- **Sections et séparateurs** (`heading`, `divider`)
|
|
69
|
+
- **API publique** — `getForm()`, `patchValues()` via `@ViewChild`
|
|
70
|
+
- **Événements** — `formSubmit`, `formChange`, `formReset`
|
|
71
|
+
- **Form Builder visuel** — interface 3 panneaux intégrée dans le plugin
|
|
72
|
+
- **Stockage abstrait** — localStorage (v1.1.0) ou API REST Spring (v1.1.0)
|
|
73
|
+
- **FormConfigService** — Signal-based, Observable API
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Installation
|
|
78
|
+
|
|
79
|
+
### 1. Prérequis
|
|
80
|
+
|
|
81
|
+
- Angular **19+**
|
|
82
|
+
- Node.js **18+**
|
|
83
|
+
|
|
84
|
+
### 2. Installer les dépendances
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
npm install @suntelecoms/ngx-dynamic-form @angular/material@^19.0.0 @angular/cdk@^19.0.0
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### 3. Ajouter le thème Material et les icônes
|
|
91
|
+
|
|
92
|
+
**angular.json :**
|
|
93
|
+
```json
|
|
94
|
+
"styles": [
|
|
95
|
+
"@angular/material/prebuilt-themes/azure-blue.css",
|
|
96
|
+
"src/styles.css"
|
|
97
|
+
]
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
**index.html :**
|
|
101
|
+
```html
|
|
102
|
+
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
|
103
|
+
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap" rel="stylesheet">
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### 4. Configurer le plugin (app.config.ts)
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
import { ApplicationConfig } from '@angular/core';
|
|
110
|
+
import { provideRouter } from '@angular/router';
|
|
111
|
+
import { provideAnimations } from '@angular/platform-browser/animations';
|
|
112
|
+
import { provideNgxForms } from '@suntelecoms/ngx-dynamic-form';
|
|
113
|
+
import { routes } from './app.routes';
|
|
114
|
+
|
|
115
|
+
export const appConfig: ApplicationConfig = {
|
|
116
|
+
providers: [
|
|
117
|
+
provideRouter(routes),
|
|
118
|
+
provideAnimations(),
|
|
119
|
+
provideNgxForms({ storage: 'local' }), // ← stockage localStorage
|
|
120
|
+
],
|
|
121
|
+
};
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### 5. Ajouter les routes (app.routes.ts)
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
import { Routes } from '@angular/router';
|
|
128
|
+
|
|
129
|
+
export const routes: Routes = [
|
|
130
|
+
// ... vos routes
|
|
131
|
+
{
|
|
132
|
+
path: 'builder',
|
|
133
|
+
loadComponent: () => import('@suntelecoms/ngx-dynamic-form').then(m => m.FormBuilderComponent),
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
path: 'mes-formulaires',
|
|
137
|
+
loadComponent: () => import('@suntelecoms/ngx-dynamic-form').then(m => m.MesFormulairesComponent),
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
path: 'form/:id',
|
|
141
|
+
loadComponent: () => import('@suntelecoms/ngx-dynamic-form').then(m => m.FormLoaderComponent),
|
|
142
|
+
},
|
|
143
|
+
];
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
**C'est tout.** Aucun code supplémentaire à écrire — le Builder, la liste des formulaires et la page de rendu sont entièrement fournis par le plugin.
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## Démarrage rapide — formulaire statique
|
|
151
|
+
|
|
152
|
+
Utilisez `<ngx-dynamic-form>` directement avec une config TypeScript :
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
import { Component } from '@angular/core';
|
|
156
|
+
import { DynamicFormComponent } from '@suntelecoms/ngx-dynamic-form';
|
|
157
|
+
import type { DynamicFormConfig, DynamicFormSubmitEvent } from '@suntelecoms/ngx-dynamic-form';
|
|
158
|
+
|
|
159
|
+
@Component({
|
|
160
|
+
selector: 'app-contact',
|
|
161
|
+
standalone: true,
|
|
162
|
+
imports: [DynamicFormComponent],
|
|
163
|
+
template: `
|
|
164
|
+
<ngx-dynamic-form [config]="config" (formSubmit)="onSubmit($event)" />
|
|
165
|
+
`,
|
|
166
|
+
})
|
|
167
|
+
export class ContactComponent {
|
|
168
|
+
config: DynamicFormConfig = {
|
|
169
|
+
submitLabel: 'Envoyer',
|
|
170
|
+
showReset: true,
|
|
171
|
+
fields: [
|
|
172
|
+
{ key: 'name', type: 'text', label: 'Nom complet', col: 6, icon: 'person',
|
|
173
|
+
validation: { required: true } },
|
|
174
|
+
{ key: 'email', type: 'email', label: 'E-mail', col: 6, icon: 'email',
|
|
175
|
+
validation: { required: true, email: true } },
|
|
176
|
+
{ key: 'message', type: 'textarea', label: 'Message', rows: 5,
|
|
177
|
+
validation: { required: true } },
|
|
178
|
+
],
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
onSubmit(event: DynamicFormSubmitEvent): void {
|
|
182
|
+
console.log(event.value);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## Démarrage rapide — stack complète
|
|
190
|
+
|
|
191
|
+
Pour disposer du **Form Builder + gestionnaire + rendu par URL** dans votre application, il suffit des 2 fichiers de configuration vus à l'installation. Après `ng serve` :
|
|
192
|
+
|
|
193
|
+
| URL | Page |
|
|
194
|
+
|---|---|
|
|
195
|
+
| `/builder` | Interface graphique de création de formulaires |
|
|
196
|
+
| `/mes-formulaires` | Liste des formulaires sauvegardés, avec rendu à la volée |
|
|
197
|
+
| `/form/abc123` | Rendu d'un formulaire sauvegardé par son ID |
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## Types de champs
|
|
202
|
+
|
|
203
|
+
| Type | Rendu | Description |
|
|
204
|
+
|---|---|---|
|
|
205
|
+
| `text` | `<input type="text">` | Champ texte standard |
|
|
206
|
+
| `email` | `<input type="email">` | Validation e-mail intégrée |
|
|
207
|
+
| `password` | `<input type="password">` | Mot de passe masqué |
|
|
208
|
+
| `number` | `<input type="number">` | Supporte `min`, `max` |
|
|
209
|
+
| `tel` | `<input type="tel">` | Téléphone |
|
|
210
|
+
| `url` | `<input type="url">` | URL |
|
|
211
|
+
| `date` | `<input type="date">` | Sélecteur de date natif |
|
|
212
|
+
| `textarea` | `<textarea>` | Zone de texte multi-lignes |
|
|
213
|
+
| `select` | `<mat-select>` | Liste déroulante — 1 choix |
|
|
214
|
+
| `multiselect` | `<mat-select multiple>` | Liste déroulante — plusieurs choix |
|
|
215
|
+
| `radio` | `<mat-radio-group>` | Boutons radio |
|
|
216
|
+
| `checkbox` | `<mat-checkbox>` | Case à cocher booléenne |
|
|
217
|
+
| `file` | `<input type="file">` | Téléversement de fichier |
|
|
218
|
+
| `hidden` | `<input type="hidden">` | Champ caché (présent dans le FormGroup) |
|
|
219
|
+
| `divider` | `<hr>` | Séparateur horizontal |
|
|
220
|
+
| `heading` | `<h1>` à `<h6>` | Titre de section |
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
## Configuration — DynamicFormConfig
|
|
225
|
+
|
|
226
|
+
```typescript
|
|
227
|
+
interface DynamicFormConfig {
|
|
228
|
+
fields: DynamicFormField[]; // Liste des champs (obligatoire)
|
|
229
|
+
submitLabel?: string; // Libellé du bouton Envoyer (défaut : "Envoyer")
|
|
230
|
+
resetLabel?: string; // Libellé du bouton Réinitialiser (défaut : "Réinitialiser")
|
|
231
|
+
showReset?: boolean; // Afficher le bouton Réinitialiser (défaut : false)
|
|
232
|
+
layout?: 'vertical' | 'horizontal' | 'inline';
|
|
233
|
+
cssClass?: string;
|
|
234
|
+
debug?: boolean; // Affiche les valeurs JSON en temps réel
|
|
235
|
+
}
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
## Propriétés d'un champ — DynamicFormField
|
|
241
|
+
|
|
242
|
+
```typescript
|
|
243
|
+
interface DynamicFormField {
|
|
244
|
+
// Obligatoire
|
|
245
|
+
key: string; // Identifiant unique (clé dans FormGroup)
|
|
246
|
+
type: FieldType;
|
|
247
|
+
|
|
248
|
+
// Affichage
|
|
249
|
+
label?: string;
|
|
250
|
+
placeholder?: string;
|
|
251
|
+
hint?: string;
|
|
252
|
+
icon?: string; // Icône préfixe (Material Icons)
|
|
253
|
+
iconSuffix?: string; // Icône suffixe (Material Icons)
|
|
254
|
+
|
|
255
|
+
// Valeur & état
|
|
256
|
+
defaultValue?: any;
|
|
257
|
+
disabled?: boolean;
|
|
258
|
+
readonly?: boolean;
|
|
259
|
+
hidden?: boolean;
|
|
260
|
+
|
|
261
|
+
// Options (select / multiselect / radio)
|
|
262
|
+
options?: SelectOption[];
|
|
263
|
+
|
|
264
|
+
// Validation
|
|
265
|
+
validation?: FieldValidation;
|
|
266
|
+
|
|
267
|
+
// Disposition
|
|
268
|
+
col?: 1|2|3|4|6|8|9|12; // Colonnes sur 12 (défaut : 12)
|
|
269
|
+
cssClass?: string;
|
|
270
|
+
|
|
271
|
+
// Affichage conditionnel
|
|
272
|
+
showWhen?: FieldCondition | FieldCondition[];
|
|
273
|
+
|
|
274
|
+
// Spécifique file
|
|
275
|
+
accept?: string; // Ex : ".pdf,.jpg"
|
|
276
|
+
multiple?: boolean;
|
|
277
|
+
|
|
278
|
+
// Spécifique textarea
|
|
279
|
+
rows?: number; // Défaut : 3
|
|
280
|
+
|
|
281
|
+
// Spécifique heading
|
|
282
|
+
text?: string;
|
|
283
|
+
level?: 1|2|3|4|5|6; // Défaut : 2
|
|
284
|
+
|
|
285
|
+
attrs?: Record<string, any>;
|
|
286
|
+
}
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
### SelectOption
|
|
290
|
+
|
|
291
|
+
```typescript
|
|
292
|
+
interface SelectOption {
|
|
293
|
+
label: string;
|
|
294
|
+
value: any;
|
|
295
|
+
disabled?: boolean;
|
|
296
|
+
group?: string;
|
|
297
|
+
}
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
---
|
|
301
|
+
|
|
302
|
+
## Validation
|
|
303
|
+
|
|
304
|
+
```typescript
|
|
305
|
+
interface FieldValidation {
|
|
306
|
+
required?: boolean;
|
|
307
|
+
email?: boolean;
|
|
308
|
+
minLength?: number;
|
|
309
|
+
maxLength?: number;
|
|
310
|
+
min?: number;
|
|
311
|
+
max?: number;
|
|
312
|
+
pattern?: string | RegExp;
|
|
313
|
+
custom?: (value: any) => string | null;
|
|
314
|
+
messages?: {
|
|
315
|
+
required?: string;
|
|
316
|
+
email?: string;
|
|
317
|
+
minLength?: string;
|
|
318
|
+
maxLength?: string;
|
|
319
|
+
min?: string;
|
|
320
|
+
max?: string;
|
|
321
|
+
pattern?: string;
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
**Exemples :**
|
|
327
|
+
|
|
328
|
+
```typescript
|
|
329
|
+
// Validateur avec message personnalisé
|
|
330
|
+
{
|
|
331
|
+
key: 'email',
|
|
332
|
+
type: 'email',
|
|
333
|
+
label: 'E-mail',
|
|
334
|
+
validation: {
|
|
335
|
+
required: true,
|
|
336
|
+
email: true,
|
|
337
|
+
messages: {
|
|
338
|
+
required: 'L\'e-mail est obligatoire.',
|
|
339
|
+
email: 'Format e-mail invalide.',
|
|
340
|
+
},
|
|
341
|
+
},
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// Validateur custom
|
|
345
|
+
{
|
|
346
|
+
key: 'password',
|
|
347
|
+
type: 'password',
|
|
348
|
+
label: 'Mot de passe',
|
|
349
|
+
validation: {
|
|
350
|
+
required: true,
|
|
351
|
+
custom: (v) => {
|
|
352
|
+
if (!v || v.length < 8) return 'Minimum 8 caractères.';
|
|
353
|
+
if (!/[A-Z]/.test(v)) return 'Au moins une majuscule.';
|
|
354
|
+
return null;
|
|
355
|
+
},
|
|
356
|
+
},
|
|
357
|
+
}
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
---
|
|
361
|
+
|
|
362
|
+
## Affichage conditionnel — showWhen
|
|
363
|
+
|
|
364
|
+
```typescript
|
|
365
|
+
// Condition unique
|
|
366
|
+
{
|
|
367
|
+
key: 'company',
|
|
368
|
+
type: 'text',
|
|
369
|
+
label: 'Entreprise',
|
|
370
|
+
showWhen: { field: 'accountType', operator: 'equals', value: 'pro' },
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Conditions multiples (logique AND)
|
|
374
|
+
{
|
|
375
|
+
key: 'discount',
|
|
376
|
+
type: 'number',
|
|
377
|
+
label: 'Remise (%)',
|
|
378
|
+
showWhen: [
|
|
379
|
+
{ field: 'accountType', operator: 'equals', value: 'pro' },
|
|
380
|
+
{ field: 'orderTotal', operator: 'greaterThan', value: 500 },
|
|
381
|
+
],
|
|
382
|
+
}
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
### Opérateurs disponibles
|
|
386
|
+
|
|
387
|
+
| Opérateur | Description |
|
|
388
|
+
|---|---|
|
|
389
|
+
| `equals` | Valeur exactement égale |
|
|
390
|
+
| `notEquals` | Valeur différente |
|
|
391
|
+
| `contains` | Contient (string ou tableau) |
|
|
392
|
+
| `greaterThan` | Valeur numérique supérieure |
|
|
393
|
+
| `lessThan` | Valeur numérique inférieure |
|
|
394
|
+
| `exists` | Champ renseigné (non vide, non null) |
|
|
395
|
+
| `notExists` | Champ vide ou null |
|
|
396
|
+
|
|
397
|
+
---
|
|
398
|
+
|
|
399
|
+
## Icônes (Material Icons)
|
|
400
|
+
|
|
401
|
+
```typescript
|
|
402
|
+
{ key: 'email', type: 'email', label: 'E-mail', icon: 'email', iconSuffix: 'verified' }
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
| Champ | Icône recommandée |
|
|
406
|
+
|---|---|
|
|
407
|
+
| Nom, Prénom | `person`, `badge` |
|
|
408
|
+
| E-mail | `email` |
|
|
409
|
+
| Téléphone | `phone` |
|
|
410
|
+
| Mot de passe | `lock` |
|
|
411
|
+
| Adresse | `home`, `location_on` |
|
|
412
|
+
| Entreprise | `business` |
|
|
413
|
+
| Prix | `euro`, `attach_money` |
|
|
414
|
+
| Date | `calendar_today` |
|
|
415
|
+
| Pièce jointe | `attach_file` |
|
|
416
|
+
|
|
417
|
+
---
|
|
418
|
+
|
|
419
|
+
## Disposition en grille (col)
|
|
420
|
+
|
|
421
|
+
La propriété `col` détermine le nombre de colonnes sur 12 occupées par le champ.
|
|
422
|
+
|
|
423
|
+
| `col` | Largeur | Usage |
|
|
424
|
+
|---|---|---|
|
|
425
|
+
| `12` | 100% | Pleine largeur (défaut) |
|
|
426
|
+
| `6` | 50% | Deux champs côte à côte |
|
|
427
|
+
| `4` | 33% | Trois champs côte à côte |
|
|
428
|
+
| `3` | 25% | Quatre champs côte à côte |
|
|
429
|
+
|
|
430
|
+
> En dessous de 600 px, tous les champs passent automatiquement en pleine largeur.
|
|
431
|
+
|
|
432
|
+
**Sections :**
|
|
433
|
+
```typescript
|
|
434
|
+
fields: [
|
|
435
|
+
{ key: '_s1', type: 'heading', text: 'Informations', level: 3, col: 12 },
|
|
436
|
+
{ key: 'name', type: 'text', label: 'Nom', col: 6 },
|
|
437
|
+
{ key: 'email', type: 'email', label: 'E-mail', col: 6 },
|
|
438
|
+
{ key: '_div', type: 'divider', col: 12 },
|
|
439
|
+
{ key: '_s2', type: 'heading', text: 'Adresse', level: 3, col: 12 },
|
|
440
|
+
]
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
---
|
|
444
|
+
|
|
445
|
+
## API du composant DynamicForm
|
|
446
|
+
|
|
447
|
+
### Inputs
|
|
448
|
+
|
|
449
|
+
| Propriété | Type | Description |
|
|
450
|
+
|---|---|---|
|
|
451
|
+
| `[config]` | `DynamicFormConfig` | Configuration du formulaire (obligatoire) |
|
|
452
|
+
| `[initialValues]` | `Record<string, any>` | Préremplissage des champs (mode édition) |
|
|
453
|
+
|
|
454
|
+
### Outputs
|
|
455
|
+
|
|
456
|
+
| Événement | Type | Description |
|
|
457
|
+
|---|---|---|
|
|
458
|
+
| `(formSubmit)` | `DynamicFormSubmitEvent` | Formulaire soumis et valide |
|
|
459
|
+
| `(formChange)` | `Record<string, any>` | Émis à chaque modification |
|
|
460
|
+
| `(formReset)` | `void` | Formulaire réinitialisé |
|
|
461
|
+
|
|
462
|
+
### API publique via @ViewChild
|
|
463
|
+
|
|
464
|
+
```typescript
|
|
465
|
+
@ViewChild('f') formRef!: DynamicFormComponent;
|
|
466
|
+
|
|
467
|
+
// Accéder au FormGroup Angular
|
|
468
|
+
const fg = this.formRef.getForm();
|
|
469
|
+
console.log(fg.value, fg.valid);
|
|
470
|
+
|
|
471
|
+
// Mettre à jour des valeurs sans réinitialiser
|
|
472
|
+
this.formRef.patchValues({ name: 'Nouveau nom' });
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
```html
|
|
476
|
+
<ngx-dynamic-form
|
|
477
|
+
#f
|
|
478
|
+
[config]="config"
|
|
479
|
+
[initialValues]="existingData"
|
|
480
|
+
(formSubmit)="onSubmit($event)"
|
|
481
|
+
(formChange)="onChange($event)"
|
|
482
|
+
/>
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
---
|
|
486
|
+
|
|
487
|
+
## provideNgxForms — configuration globale
|
|
488
|
+
|
|
489
|
+
`provideNgxForms()` est la fonction de configuration du plugin. Elle enregistre le `FormConfigService` et l'adaptateur de stockage dans le contexte Angular.
|
|
490
|
+
|
|
491
|
+
### Stockage localStorage (défaut)
|
|
492
|
+
|
|
493
|
+
```typescript
|
|
494
|
+
// app.config.ts
|
|
495
|
+
import { provideNgxForms } from '@suntelecoms/ngx-dynamic-form';
|
|
496
|
+
|
|
497
|
+
providers: [
|
|
498
|
+
provideNgxForms({ storage: 'local' }),
|
|
499
|
+
]
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
### Stockage API REST (Spring Boot)
|
|
503
|
+
|
|
504
|
+
```typescript
|
|
505
|
+
// app.config.ts
|
|
506
|
+
providers: [
|
|
507
|
+
provideNgxForms({
|
|
508
|
+
storage: 'http',
|
|
509
|
+
apiUrl: 'https://api.monprojet.com/forms',
|
|
510
|
+
}),
|
|
511
|
+
provideHttpClient(), // requis pour le mode http
|
|
512
|
+
]
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
L'API doit exposer les endpoints suivants :
|
|
516
|
+
|
|
517
|
+
```
|
|
518
|
+
GET /forms → SavedFormSchema[]
|
|
519
|
+
GET /forms/:id → SavedFormSchema
|
|
520
|
+
PUT /forms/:id → SavedFormSchema (création ou mise à jour)
|
|
521
|
+
DELETE /forms/:id → void
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
---
|
|
525
|
+
|
|
526
|
+
## FormConfigService — gestion des schémas
|
|
527
|
+
|
|
528
|
+
`FormConfigService` est fourni par `provideNgxForms()`. Il gère les schémas de formulaires avec un **Signal réactif** et une **API Observable**.
|
|
529
|
+
|
|
530
|
+
```typescript
|
|
531
|
+
import { inject } from '@angular/core';
|
|
532
|
+
import { FormConfigService } from '@suntelecoms/ngx-dynamic-form';
|
|
533
|
+
|
|
534
|
+
export class MonComposant {
|
|
535
|
+
private formService = inject(FormConfigService);
|
|
536
|
+
|
|
537
|
+
// Signal réactif — mise à jour automatique
|
|
538
|
+
readonly schemas = this.formService.schemas; // Signal<SavedFormSchema[]>
|
|
539
|
+
|
|
540
|
+
// Lecture synchrone
|
|
541
|
+
getById(id: string) {
|
|
542
|
+
return this.formService.get(id); // SavedFormSchema | null
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// Sauvegarde — Observable (complète après écriture)
|
|
546
|
+
saveForm(id: string, title: string, config: DynamicFormConfig) {
|
|
547
|
+
this.formService.save(id, title, config).subscribe(schema => {
|
|
548
|
+
console.log('Sauvegardé :', schema);
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// Suppression — Observable
|
|
553
|
+
deleteForm(id: string) {
|
|
554
|
+
this.formService.delete(id).subscribe(() => {
|
|
555
|
+
console.log('Supprimé');
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// Génère un ID unique
|
|
560
|
+
newId() {
|
|
561
|
+
return this.formService.generateId();
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
### Interface SavedFormSchema
|
|
567
|
+
|
|
568
|
+
```typescript
|
|
569
|
+
interface SavedFormSchema {
|
|
570
|
+
id: string; // Identifiant unique
|
|
571
|
+
title: string; // Nom du formulaire
|
|
572
|
+
config: DynamicFormConfig; // Configuration complète
|
|
573
|
+
updatedAt: string; // ISO 8601
|
|
574
|
+
}
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
---
|
|
578
|
+
|
|
579
|
+
## Stockage — LocalStorage vs API REST
|
|
580
|
+
|
|
581
|
+
Le plugin utilise une abstraction `FormStorageAdapter` qui permet de changer le backend de stockage sans toucher au reste de l'application.
|
|
582
|
+
|
|
583
|
+
### Résumé des adaptateurs
|
|
584
|
+
|
|
585
|
+
| Option | Classe | Quand utiliser |
|
|
586
|
+
|---|---|---|
|
|
587
|
+
| `storage: 'local'` | `LocalStorageAdapter` | Développement, démo, usage hors-ligne |
|
|
588
|
+
| `storage: 'http'` | `HttpStorageAdapter` | Production avec backend Spring Boot |
|
|
589
|
+
|
|
590
|
+
### Clé localStorage
|
|
591
|
+
|
|
592
|
+
Quand `storage: 'local'`, les données sont stockées sous :
|
|
593
|
+
|
|
594
|
+
```
|
|
595
|
+
localStorage['ngx-dynamic-form__schemas'] = [ { id, title, config, updatedAt }, ... ]
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
### Implémenter son propre adaptateur
|
|
599
|
+
|
|
600
|
+
```typescript
|
|
601
|
+
import { Injectable } from '@angular/core';
|
|
602
|
+
import { Observable, of } from 'rxjs';
|
|
603
|
+
import { FormStorageAdapter, SavedFormSchema } from '@suntelecoms/ngx-dynamic-form';
|
|
604
|
+
|
|
605
|
+
@Injectable()
|
|
606
|
+
export class IndexedDBAdapter extends FormStorageAdapter {
|
|
607
|
+
list(): Observable<SavedFormSchema[]> { /* ... */ }
|
|
608
|
+
get(id: string): Observable<SavedFormSchema | null> { /* ... */ }
|
|
609
|
+
save(schema: SavedFormSchema): Observable<SavedFormSchema> { /* ... */ }
|
|
610
|
+
delete(id: string): Observable<void> { /* ... */ }
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// app.config.ts
|
|
614
|
+
providers: [
|
|
615
|
+
{ provide: FormStorageAdapter, useClass: IndexedDBAdapter },
|
|
616
|
+
FormConfigService,
|
|
617
|
+
]
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
---
|
|
621
|
+
|
|
622
|
+
## Form Builder — éditeur visuel
|
|
623
|
+
|
|
624
|
+
Le **Form Builder** est une interface 3 panneaux intégrée directement dans le plugin via le composant `<ngx-form-builder>` (sélecteur `ngx-form-builder`).
|
|
625
|
+
|
|
626
|
+
### Ajouter la route
|
|
627
|
+
|
|
628
|
+
```typescript
|
|
629
|
+
{
|
|
630
|
+
path: 'builder',
|
|
631
|
+
loadComponent: () => import('@suntelecoms/ngx-dynamic-form').then(m => m.FormBuilderComponent),
|
|
632
|
+
}
|
|
633
|
+
```
|
|
634
|
+
|
|
635
|
+
### Interface
|
|
636
|
+
|
|
637
|
+
```
|
|
638
|
+
┌──────────────────┬───────────────────────────────┬─────────────────────┐
|
|
639
|
+
│ Palette │ Schéma / Aperçu │ Propriétés │
|
|
640
|
+
│ │ │ (champ sélect.) │
|
|
641
|
+
│ Saisie │ ┌────────────────────────┐ │ │
|
|
642
|
+
│ [Texte] │ │ 👤 Nom (text) ↑↓⎘🗑│ │ Label : Nom │
|
|
643
|
+
│ [E-mail] │ │ 📧 Email (email) ↑↓⎘🗑│ │ Col : 6/12 │
|
|
644
|
+
│ [Nombre] │ └────────────────────────┘ │ Icône : person │
|
|
645
|
+
│ ... │ │ Required : ✓ │
|
|
646
|
+
│ Choix │ Paramètres du formulaire │ │
|
|
647
|
+
│ [Select] ├───────────────────────────────┤ Validation │
|
|
648
|
+
│ [Radio] │ Bouton Envoyer : [Envoyer] │ Options │
|
|
649
|
+
│ ... │ [ ] Afficher Réinitialiser │ │
|
|
650
|
+
│ │ │ │
|
|
651
|
+
│ Formulaires │ [Schéma] [Aperçu] │ │
|
|
652
|
+
│ sauvegardés │ │ │
|
|
653
|
+
└──────────────────┴───────────────────────────────┴─────────────────────┘
|
|
654
|
+
```
|
|
655
|
+
|
|
656
|
+
### Fonctionnalités du Builder
|
|
657
|
+
|
|
658
|
+
| Action | Description |
|
|
659
|
+
|---|---|
|
|
660
|
+
| Ajouter un champ | Clic sur un type dans la palette |
|
|
661
|
+
| Réordonner | Boutons ↑ ↓ sur chaque champ |
|
|
662
|
+
| Dupliquer | Copie d'un champ avec toutes ses propriétés |
|
|
663
|
+
| Supprimer | Icône corbeille |
|
|
664
|
+
| Éditer | Panneau droit — label, col, icône, validation, options… |
|
|
665
|
+
| Aperçu | Onglet **Aperçu** — rendu réel via `<ngx-dynamic-form>` |
|
|
666
|
+
| Sauvegarder | Stocke en localStorage (ou API) avec ID unique |
|
|
667
|
+
| Export JSON | JSON de la config dans un modal copiable |
|
|
668
|
+
| Export TypeScript | `const config: DynamicFormConfig = { ... }` |
|
|
669
|
+
| Partager | Copie l'URL `/form/<id>` dans le presse-papiers |
|
|
670
|
+
|
|
671
|
+
### Inputs du FormBuilderComponent
|
|
672
|
+
|
|
673
|
+
| Input | Type | Défaut | Description |
|
|
674
|
+
|---|---|---|---|
|
|
675
|
+
| `[backRoute]` | `string` | `'/'` | Route du lien "Retour" dans la topbar |
|
|
676
|
+
|
|
677
|
+
---
|
|
678
|
+
|
|
679
|
+
## Composants de navigation
|
|
680
|
+
|
|
681
|
+
### MesFormulairesComponent — `<ngx-mes-formulaires>`
|
|
682
|
+
|
|
683
|
+
Liste et aperçu de tous les formulaires sauvegardés. Permet de les prévisualiser, les soumettre, les supprimer.
|
|
684
|
+
|
|
685
|
+
```typescript
|
|
686
|
+
{
|
|
687
|
+
path: 'mes-formulaires',
|
|
688
|
+
loadComponent: () => import('@suntelecoms/ngx-dynamic-form').then(m => m.MesFormulairesComponent),
|
|
689
|
+
}
|
|
690
|
+
```
|
|
691
|
+
|
|
692
|
+
| Input | Type | Défaut | Description |
|
|
693
|
+
|---|---|---|---|
|
|
694
|
+
| `[backRoute]` | `string` | `'/'` | Lien "Retour" |
|
|
695
|
+
| `[builderRoute]` | `string` | `'/builder'` | Route vers le Builder |
|
|
696
|
+
| `[formRoute]` | `string` | `'/form'` | Préfixe de la route de rendu (+ `/:id`) |
|
|
697
|
+
|
|
698
|
+
### FormLoaderComponent — `<ngx-form-loader>`
|
|
699
|
+
|
|
700
|
+
Charge et affiche un formulaire depuis son ID (via `ActivatedRoute` ou `@Input`).
|
|
701
|
+
|
|
702
|
+
```typescript
|
|
703
|
+
{
|
|
704
|
+
path: 'form/:id',
|
|
705
|
+
loadComponent: () => import('@suntelecoms/ngx-dynamic-form').then(m => m.FormLoaderComponent),
|
|
706
|
+
}
|
|
707
|
+
```
|
|
708
|
+
|
|
709
|
+
| Input | Type | Défaut | Description |
|
|
710
|
+
|---|---|---|---|
|
|
711
|
+
| `[formId]` | `string` | — | ID direct (sans routing, optionnel) |
|
|
712
|
+
| `[backRoute]` | `string` | `'/'` | Lien "Retour" |
|
|
713
|
+
| `[builderRoute]` | `string` | `'/builder'` | Lien "Éditer" |
|
|
714
|
+
|
|
715
|
+
---
|
|
716
|
+
|
|
717
|
+
## Intégration dans un composant
|
|
718
|
+
|
|
719
|
+
### Approche A — chargement dynamique via FormConfigService
|
|
720
|
+
|
|
721
|
+
```typescript
|
|
722
|
+
import { Component, computed, inject, signal } from '@angular/core';
|
|
723
|
+
import { DynamicFormComponent, FormConfigService } from '@suntelecoms/ngx-dynamic-form';
|
|
724
|
+
import type { DynamicFormConfig, DynamicFormSubmitEvent } from '@suntelecoms/ngx-dynamic-form';
|
|
725
|
+
|
|
726
|
+
@Component({
|
|
727
|
+
standalone: true,
|
|
728
|
+
imports: [DynamicFormComponent],
|
|
729
|
+
template: `
|
|
730
|
+
@if (activeConfig(); as cfg) {
|
|
731
|
+
<ngx-dynamic-form [config]="cfg" (formSubmit)="onSubmit($event)" />
|
|
732
|
+
}
|
|
733
|
+
`,
|
|
734
|
+
})
|
|
735
|
+
export class MonFormulaireComponent {
|
|
736
|
+
private formService = inject(FormConfigService);
|
|
737
|
+
|
|
738
|
+
// Signal réactif — liste des formulaires disponibles
|
|
739
|
+
readonly schemas = this.formService.schemas;
|
|
740
|
+
|
|
741
|
+
// Sélection de l'ID actif
|
|
742
|
+
selectedId = signal('votre-id-ici');
|
|
743
|
+
|
|
744
|
+
// Config dérivée automatiquement
|
|
745
|
+
activeConfig = computed(() =>
|
|
746
|
+
this.formService.get(this.selectedId())?.config ?? null
|
|
747
|
+
);
|
|
748
|
+
|
|
749
|
+
onSubmit(event: DynamicFormSubmitEvent): void {
|
|
750
|
+
console.log(event.value);
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
```
|
|
754
|
+
|
|
755
|
+
### Approche B — configuration statique TypeScript
|
|
756
|
+
|
|
757
|
+
```typescript
|
|
758
|
+
import { Component } from '@angular/core';
|
|
759
|
+
import { DynamicFormComponent } from '@suntelecoms/ngx-dynamic-form';
|
|
760
|
+
import type { DynamicFormConfig, DynamicFormSubmitEvent } from '@suntelecoms/ngx-dynamic-form';
|
|
761
|
+
|
|
762
|
+
@Component({
|
|
763
|
+
standalone: true,
|
|
764
|
+
imports: [DynamicFormComponent],
|
|
765
|
+
template: `<ngx-dynamic-form [config]="config" (formSubmit)="onSubmit($event)" />`,
|
|
766
|
+
})
|
|
767
|
+
export class ContactComponent {
|
|
768
|
+
config: DynamicFormConfig = {
|
|
769
|
+
submitLabel: 'Envoyer',
|
|
770
|
+
fields: [
|
|
771
|
+
{ key: 'nom', type: 'text', label: 'Nom', col: 6, validation: { required: true } },
|
|
772
|
+
{ key: 'email', type: 'email', label: 'E-mail', col: 6, validation: { required: true } },
|
|
773
|
+
],
|
|
774
|
+
};
|
|
775
|
+
|
|
776
|
+
onSubmit(event: DynamicFormSubmitEvent): void {
|
|
777
|
+
console.log(event.value);
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
```
|
|
781
|
+
|
|
782
|
+
> **Astuce :** créez le formulaire dans le Builder (`/builder`) → cliquez **Export TypeScript** → collez dans votre composant.
|
|
783
|
+
|
|
784
|
+
### Approche C — avec backend API REST (Spring Boot)
|
|
785
|
+
|
|
786
|
+
Configurez simplement `provideNgxForms({ storage: 'http', apiUrl: '...' })` — tout le reste est identique aux approches A et B. Le `FormConfigService` appellera automatiquement votre API au lieu de localStorage.
|
|
787
|
+
|
|
788
|
+
```typescript
|
|
789
|
+
// app.config.ts
|
|
790
|
+
provideNgxForms({
|
|
791
|
+
storage: 'http',
|
|
792
|
+
apiUrl: 'https://api.monprojet.com/forms',
|
|
793
|
+
}),
|
|
794
|
+
provideHttpClient(),
|
|
795
|
+
```
|
|
796
|
+
|
|
797
|
+
```java
|
|
798
|
+
// Exemple endpoint Spring Boot
|
|
799
|
+
@RestController
|
|
800
|
+
@RequestMapping("/forms")
|
|
801
|
+
public class FormController {
|
|
802
|
+
@GetMapping List<SavedFormSchema> list() { ... }
|
|
803
|
+
@GetMapping("/{id}") SavedFormSchema get(@PathVariable String id) { ... }
|
|
804
|
+
@PutMapping("/{id}") SavedFormSchema save(@PathVariable String id, @RequestBody SavedFormSchema s) { ... }
|
|
805
|
+
@DeleteMapping("/{id}") void delete(@PathVariable String id) { ... }
|
|
806
|
+
}
|
|
807
|
+
```
|
|
808
|
+
|
|
809
|
+
---
|
|
810
|
+
|
|
811
|
+
## Pages de démonstration
|
|
812
|
+
|
|
813
|
+
L'application de démonstration (`ng serve`) illustre le plugin sur plusieurs cas d'usage :
|
|
814
|
+
|
|
815
|
+
| Route | Description |
|
|
816
|
+
|---|---|
|
|
817
|
+
| `/` | Page d'accueil |
|
|
818
|
+
| `/inscription` | Formulaire multi-sections avec `showWhen` |
|
|
819
|
+
| `/contact` | Formulaire de contact (select, textarea, radio) |
|
|
820
|
+
| `/produit` | Fiche produit — modes vue / édition / création |
|
|
821
|
+
| `/builder` | **Form Builder** — composant `FormBuilderComponent` de la lib |
|
|
822
|
+
| `/mes-formulaires` | **Gestionnaire** — composant `MesFormulairesComponent` de la lib |
|
|
823
|
+
| `/form/:id` | **Rendu par ID** — composant `FormLoaderComponent` de la lib |
|
|
824
|
+
|
|
825
|
+
---
|
|
826
|
+
|
|
827
|
+
## Exemples complets
|
|
828
|
+
|
|
829
|
+
### Formulaire d'inscription avec showWhen
|
|
830
|
+
|
|
831
|
+
```typescript
|
|
832
|
+
config: DynamicFormConfig = {
|
|
833
|
+
submitLabel: 'Créer mon compte',
|
|
834
|
+
showReset: true,
|
|
835
|
+
fields: [
|
|
836
|
+
{ key: '_info', type: 'heading', text: 'Informations', level: 3, col: 12 },
|
|
837
|
+
{ key: 'firstName', type: 'text', label: 'Prénom', col: 6, icon: 'person',
|
|
838
|
+
validation: { required: true } },
|
|
839
|
+
{ key: 'lastName', type: 'text', label: 'Nom', col: 6, icon: 'badge',
|
|
840
|
+
validation: { required: true } },
|
|
841
|
+
{ key: 'email', type: 'email', label: 'E-mail', col: 12, icon: 'email',
|
|
842
|
+
validation: { required: true, email: true } },
|
|
843
|
+
{ key: '_div', type: 'divider', col: 12 },
|
|
844
|
+
{ key: 'accountType', type: 'radio', label: 'Type de compte', col: 12,
|
|
845
|
+
defaultValue: 'personal',
|
|
846
|
+
options: [
|
|
847
|
+
{ label: 'Particulier', value: 'personal' },
|
|
848
|
+
{ label: 'Professionnel', value: 'pro' },
|
|
849
|
+
] },
|
|
850
|
+
{ key: 'company', type: 'text', label: 'Entreprise', col: 6, icon: 'business',
|
|
851
|
+
showWhen: { field: 'accountType', operator: 'equals', value: 'pro' } },
|
|
852
|
+
{ key: 'siret', type: 'text', label: 'SIRET', col: 6, icon: 'tag',
|
|
853
|
+
showWhen: { field: 'accountType', operator: 'equals', value: 'pro' },
|
|
854
|
+
validation: { minLength: 14, maxLength: 14 } },
|
|
855
|
+
{ key: 'terms', type: 'checkbox',
|
|
856
|
+
placeholder: "J'accepte les CGU *", col: 12,
|
|
857
|
+
validation: { required: true, messages: { required: 'Vous devez accepter les CGU.' } } },
|
|
858
|
+
],
|
|
859
|
+
};
|
|
860
|
+
```
|
|
861
|
+
|
|
862
|
+
### Fiche produit avec préremplissage et mode vue/édition
|
|
863
|
+
|
|
864
|
+
```typescript
|
|
865
|
+
mode = signal<'view' | 'edit'>('edit');
|
|
866
|
+
prefill = { name: 'Casque Pro X1', price: 129.99, stock: 42 };
|
|
867
|
+
|
|
868
|
+
get config(): DynamicFormConfig {
|
|
869
|
+
const ro = this.mode() === 'view';
|
|
870
|
+
return {
|
|
871
|
+
submitLabel: 'Sauvegarder',
|
|
872
|
+
fields: [
|
|
873
|
+
{ key: 'name', type: 'text', label: 'Nom', col: 8, icon: 'inventory', readonly: ro,
|
|
874
|
+
validation: { required: true } },
|
|
875
|
+
{ key: 'price', type: 'number', label: 'Prix', col: 4, icon: 'euro', readonly: ro },
|
|
876
|
+
{ key: 'stock', type: 'number', label: 'Stock', col: 4, icon: 'inventory_2', readonly: ro },
|
|
877
|
+
],
|
|
878
|
+
};
|
|
879
|
+
}
|
|
880
|
+
```
|
|
881
|
+
|
|
882
|
+
```html
|
|
883
|
+
<ngx-dynamic-form
|
|
884
|
+
[config]="config"
|
|
885
|
+
[initialValues]="prefill"
|
|
886
|
+
(formSubmit)="onSubmit($event)"
|
|
887
|
+
/>
|
|
888
|
+
```
|
|
889
|
+
|
|
890
|
+
---
|
|
891
|
+
|
|
892
|
+
## Personnalisation CSS
|
|
893
|
+
|
|
894
|
+
```css
|
|
895
|
+
/* styles.css */
|
|
896
|
+
:root {
|
|
897
|
+
--ngx-form-gap: 1.25rem;
|
|
898
|
+
--ngx-color-border: #e2e8f0;
|
|
899
|
+
--ngx-color-text: #1a202c;
|
|
900
|
+
--ngx-color-hint: #718096;
|
|
901
|
+
}
|
|
902
|
+
```
|
|
903
|
+
|
|
904
|
+
Pour les couleurs des champs Material (outline, focus, erreur), personnalisez le thème Angular Material selon la [documentation officielle](https://material.angular.io/guide/theming).
|
|
905
|
+
|
|
906
|
+
---
|
|
907
|
+
|
|
908
|
+
## Interfaces TypeScript
|
|
909
|
+
|
|
910
|
+
```typescript
|
|
911
|
+
// Exports publics de @suntelecoms/ngx-dynamic-form
|
|
912
|
+
|
|
913
|
+
// Composants
|
|
914
|
+
export { DynamicFormComponent } // <ngx-dynamic-form>
|
|
915
|
+
export { FormBuilderComponent } // <ngx-form-builder>
|
|
916
|
+
export { MesFormulairesComponent } // <ngx-mes-formulaires>
|
|
917
|
+
export { FormLoaderComponent } // <ngx-form-loader>
|
|
918
|
+
|
|
919
|
+
// Services & Providers
|
|
920
|
+
export { FormConfigService } // inject(FormConfigService)
|
|
921
|
+
export { FormStorageAdapter } // classe abstraite de stockage
|
|
922
|
+
export { LocalStorageAdapter } // implémentation localStorage
|
|
923
|
+
export { HttpStorageAdapter } // implémentation API REST
|
|
924
|
+
export { FORM_API_URL } // InjectionToken<string>
|
|
925
|
+
export { provideNgxForms } // function(options) → EnvironmentProviders
|
|
926
|
+
|
|
927
|
+
// Types
|
|
928
|
+
export type { NgxFormsOptions } // { storage: 'local' } | { storage: 'http', apiUrl: string }
|
|
929
|
+
export type { SavedFormSchema } // { id, title, config, updatedAt }
|
|
930
|
+
export type { DynamicFormConfig }
|
|
931
|
+
export type { DynamicFormField }
|
|
932
|
+
export type { DynamicFormSubmitEvent }
|
|
933
|
+
export type { FieldType }
|
|
934
|
+
export type { FieldValidation }
|
|
935
|
+
export type { FieldCondition }
|
|
936
|
+
export type { SelectOption }
|
|
937
|
+
```
|
|
938
|
+
|
|
939
|
+
---
|
|
940
|
+
|
|
941
|
+
## Auteur
|
|
942
|
+
|
|
943
|
+
**Amadou Diallo** — [@amepro](https://github.com/amepro)
|
|
944
|
+
📧 [amadoumacbookpro38@gmail.com](mailto:amadoumacbookpro38@gmail.com)
|
|
945
|
+
|
|
946
|
+
Développé dans le cadre du projet **SUNTELECOMS** — Plugin Angular 19 standalone.
|
|
947
|
+
|
|
948
|
+
---
|
|
949
|
+
|
|
950
|
+
*MIT License — Libre d'utilisation, de modification et de distribution.*
|