@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 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
+ [![Angular](https://img.shields.io/badge/Angular-19-red?logo=angular)](https://angular.dev)
6
+ [![Angular Material](https://img.shields.io/badge/Angular_Material-19-blue?logo=material-design)](https://material.angular.io)
7
+ [![Version](https://img.shields.io/badge/version-1.1.0-brightgreen)](CHANGELOG.md)
8
+ [![License](https://img.shields.io/badge/License-MIT-green)](LICENSE)
9
+ [![Author](https://img.shields.io/badge/Author-Amadou%20Diallo-purple)](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.*