npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@suntelecoms/ngx-dynamic-form

v1.3.2

Published

Générateur de formulaires Angular dynamiques — SUNTELECOMS

Readme

@suntelecoms/ngx-dynamic-form

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, un système de stockage interchangeable (localStorage / API REST) et le support des formulaires multi-étapes (Stepper).

Angular Angular Material Version License Author


Sommaire


Présentation

@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.

Depuis la v1.2.0, le plugin embarque :

  • Le Form Builder visuel (<ngx-form-builder>) — avec mode Stepper
  • Le gestionnaire de formulaires (<ngx-mes-formulaires>)
  • La page de rendu par code (<ngx-form-loader>)
  • Un système de stockage abstrait (localStorage par défaut, API REST Spring en option)
  • Les formulaires multi-étapes (<ngx-stepper-form>) avec étape de récapitulatif

Résultat : dans n'importe quel projet Angular, il suffit de 2 lignes pour avoir une interface complète de paramétrage de formulaires.


Fonctionnalités

  • 24 types de champstext, email, password, number, tel, url, date, datetime-local, time, month, week, textarea, select, multiselect, radio, checkbox, switch, range, color, location, file, hidden, divider, heading
  • 3 variantes d'apparenceoutline, fill, simple (label fixe au-dessus) — globale ou par champ (v1.2.7)
  • Datepicker Material (mat-datepicker) pour le type date (v1.2.8)
  • Géolocalisation — type location avec bouton GPS et API navigator.geolocation (v1.2.8)
  • Curseur de valeur — type range rendu en mat-slider (v1.2.8)
  • Interrupteur — type switch rendu en mat-slide-toggle (v1.2.8)
  • Styling par champfontSize, textAlign, color, backgroundColor, borderColor, labelColor, fontWeight, fontStyle, cssClass, styles (v1.2.7)
  • Masque de saisie — propriété mask (# = chiffre, autres chars = séparateurs) (v1.2.7)
  • Préfixe / suffixe texte — propriétés prefix et suffix (v1.2.7)
  • Code unique par formulaire — propriété code saisie par l'utilisateur, utilisée pour l'URL de partage et la récupération par code (v1.2.9)
  • Authentification Bearer tokenauth dans provideNgxForms + intercepteur ngxFormsAuthInterceptor pour les APIs sécurisées (login/mot de passe → JWT) (v1.3.0)
  • Floating label Angular Material (mat-form-field appearance="outline")
  • Validation déclarativerequired, email, minLength, maxLength, min, max, pattern, validateur personnalisé
  • Messages d'erreur personnalisés par champ
  • Affichage conditionnel (showWhen) avec 7 opérateurs
  • Conditions multiples (logique AND)
  • Grille 12 colonnes — propriété col
  • Icônes Material Icons — préfixe et suffixe par champ
  • Préremplissage via [initialValues]
  • Mode lecture seule / désactivé par champ
  • Sections et séparateurs (heading, divider)
  • API publiquegetForm(), patchValues() via @ViewChild
  • ÉvénementsformSubmit, formChange, formReset
  • Formulaires multi-étapes (Stepper)<ngx-stepper-form> avec navigation, validation par étape et récapitulatif automatique (v1.2.0)
  • Form Builder visuel — interface 3 panneaux, supporte les modes Formulaire et Stepper (v1.2.0)
  • Options dynamiques depuis une API — propriétés url, displayName, displayValue sur les champs select, multiselect, radio (v1.2.5)
  • Stockage abstrait — localStorage ou API REST Spring
  • FormConfigService — Signal-based, Observable API

Installation

1. Prérequis

  • Angular 19+
  • Node.js 18+

2. Installer les dépendances

npm install @suntelecoms/ngx-dynamic-form @angular/material@^19.0.0 @angular/cdk@^19.0.0

3. Ajouter le thème Material et les icônes

angular.json :

"styles": [
  "@angular/material/prebuilt-themes/azure-blue.css",
  "src/styles.css"
]

index.html :

<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap" rel="stylesheet">

4. Configurer le plugin (app.config.ts)

import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router';
import { provideAnimations } from '@angular/platform-browser/animations';
import { provideNgxForms } from '@suntelecoms/ngx-dynamic-form';
import { routes } from './app.routes';

export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(routes),
    provideAnimations(),
    provideNgxForms({ storage: 'local' }),  // ← stockage localStorage
  ],
};

5. Ajouter les routes (app.routes.ts)

import { Routes } from '@angular/router';

export const routes: Routes = [
  {
    path: 'builder',
    loadComponent: () => import('@suntelecoms/ngx-dynamic-form').then(m => m.FormBuilderComponent),
  },
  {
    path: 'mes-formulaires',
    loadComponent: () => import('@suntelecoms/ngx-dynamic-form').then(m => m.MesFormulairesComponent),
  },
  {
    path: 'form/:code',   // ← le formulaire est identifié par son code unique
    loadComponent: () => import('@suntelecoms/ngx-dynamic-form').then(m => m.FormLoaderComponent),
  },
];

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.


Démarrage rapide — formulaire statique

Utilisez <ngx-dynamic-form> directement avec une config TypeScript :

import { Component } from '@angular/core';
import { DynamicFormComponent } from '@suntelecoms/ngx-dynamic-form';
import type { DynamicFormConfig, DynamicFormSubmitEvent } from '@suntelecoms/ngx-dynamic-form';

@Component({
  selector: 'app-contact',
  standalone: true,
  imports: [DynamicFormComponent],
  template: `
    <ngx-dynamic-form [config]="config" (formSubmit)="onSubmit($event)" />
  `,
})
export class ContactComponent {
  config: DynamicFormConfig = {
    submitLabel: 'Envoyer',
    showReset: true,
    fields: [
      { key: 'name',  type: 'text',  label: 'Nom complet', col: 6, icon: 'person',
        validation: { required: true } },
      { key: 'email', type: 'email', label: 'E-mail',      col: 6, icon: 'email',
        validation: { required: true, email: true } },
      { key: 'message', type: 'textarea', label: 'Message', rows: 5,
        validation: { required: true } },
    ],
  };

  onSubmit(event: DynamicFormSubmitEvent): void {
    console.log(event.value);
  }
}

Démarrage rapide — stack complète

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 :

| URL | Page | |---|---| | /builder | Interface graphique de création de formulaires (simple ou stepper) | | /mes-formulaires | Liste des formulaires sauvegardés, avec rendu à la volée | | /form/mon-code | Rendu d'un formulaire sauvegardé par son code unique |


Types de champs

Saisie texte

| Type | Rendu | Description | |---|---|---| | text | <input type="text"> | Champ texte standard | | email | <input type="email"> | Validation e-mail intégrée | | password | <input type="password"> | Mot de passe masqué | | number | <input type="number"> | Supporte min, max | | tel | <input type="tel"> | Téléphone — compatible masque de saisie | | url | <input type="url"> | URL | | textarea | <textarea> | Zone de texte multi-lignes, propriété rows |

Date & Heure

| Type | Rendu | Description | |---|---|---| | date | mat-datepicker | Calendrier Material — popup avec sélection mois/année (v1.2.8) | | datetime-local | <input type="datetime-local"> | Date et heure combinées | | time | <input type="time"> | Heure (HH:mm) | | month | <input type="month"> | Mois et année | | week | <input type="week"> | Semaine ISO |

Choix

| Type | Rendu | Description | |---|---|---| | select | <mat-select> | Liste déroulante — 1 choix — options statiques ou API | | multiselect | <mat-select multiple> | Liste déroulante — plusieurs choix | | radio | <mat-radio-group> | Boutons radio — options statiques ou API | | checkbox | <mat-checkbox> | Case à cocher booléenne | | switch | <mat-slide-toggle> | Interrupteur on/off (v1.2.8) |

Spéciaux

| Type | Rendu | Description | |---|---|---| | range | <mat-slider> | Curseur de valeur — propriétés min, max, step, showValue (v1.2.8) | | color | <input type="color"> | Sélecteur de couleur avec affichage du code hex (v1.2.8) | | location | <input> + bouton GPS | Champ adresse avec géolocalisation — bouton my_location déclenche navigator.geolocation (v1.2.8) | | file | <input type="file"> | Téléversement — propriétés accept, multiple |

Mise en page

| Type | Rendu | Description | |---|---|---| | hidden | <input type="hidden"> | Champ caché (présent dans le FormGroup) | | divider | <hr> | Séparateur horizontal | | heading | <h1> à <h6> | Titre de section — propriétés text, level |


Configuration — DynamicFormConfig

interface DynamicFormConfig {
  fields: DynamicFormField[];     // Liste des champs (obligatoire)
  submitLabel?: string;           // Libellé du bouton Envoyer (défaut : "Envoyer")
  resetLabel?: string;            // Libellé du bouton Réinitialiser (défaut : "Réinitialiser")
  showReset?: boolean;            // Afficher le bouton Réinitialiser (défaut : false)
  layout?: 'vertical' | 'horizontal' | 'inline';
  cssClass?: string;
  debug?: boolean;                // Affiche les valeurs JSON en temps réel
  // Apparence globale ← v1.2.7
  appearance?: 'outline' | 'fill' | 'simple'; // défaut : 'outline'
  floatLabel?: 'auto' | 'always';             // défaut : 'auto'
}

Propriétés d'un champ — DynamicFormField

interface DynamicFormField {
  // Obligatoire
  key: string;                    // Identifiant unique (clé dans FormGroup)
  type: FieldType;

  // Affichage
  label?: string;
  placeholder?: string;
  hint?: string;
  icon?: string;                  // Icône préfixe (Material Icons)
  iconSuffix?: string;            // Icône suffixe (Material Icons)
  prefix?: string;                // Texte préfixe dans le champ (ex : '+221')  ← v1.2.7
  suffix?: string;                // Texte suffixe dans le champ (ex : 'FCFA')  ← v1.2.7

  // Valeur & état
  defaultValue?: any;
  disabled?: boolean;
  readonly?: boolean;
  hidden?: boolean;

  // Options statiques (select / multiselect / radio)
  options?: SelectOption[];

  // Options dynamiques chargées depuis une API  ← v1.2.5
  url?: string;          // endpoint relatif ('villes') ou absolu ('https://...')
  displayName?: string;  // propriété de la réponse API utilisée comme label  (défaut : 'label')
  displayValue?: string; // propriété de la réponse API utilisée comme valeur (défaut : 'value')

  // Validation
  validation?: FieldValidation;

  // Disposition
  col?: 1|2|3|4|6|8|9|12;       // Colonnes sur 12 (défaut : 12)
  cssClass?: string;             // Classe CSS ajoutée sur l'hôte  ← v1.2.7

  // Affichage conditionnel
  showWhen?: FieldCondition | FieldCondition[];

  // Spécifique file
  accept?: string;                // Ex : ".pdf,.jpg"
  multiple?: boolean;

  // Spécifique textarea
  rows?: number;                  // Défaut : 3

  // Spécifique heading
  text?: string;
  level?: 1|2|3|4|5|6;          // Défaut : 2

  // Spécifique range  ← v1.2.8
  min?: number;                   // Valeur minimale du curseur (défaut : 0)
  max?: number;                   // Valeur maximale du curseur (défaut : 100)
  step?: number;                  // Pas du curseur (défaut : 1)
  showValue?: boolean;            // Afficher la valeur courante à côté du curseur

  // Masque de saisie  ← v1.2.7
  // '#' = chiffre obligatoire, tout autre caractère = séparateur littéral
  // Ex : '## ### ## ##' → téléphone SN, '##/##/####' → date, '#### #### #### ####' → CB
  mask?: string;

  // Apparence individuelle (override la valeur globale du formulaire)  ← v1.2.7
  appearance?: 'outline' | 'fill' | 'simple';
  floatLabel?: 'auto' | 'always';

  // Style & Apparence  ← v1.2.7 / v1.2.8
  fontSize?: string;                        // Ex : '14px', '1.1rem'
  textAlign?: 'left' | 'center' | 'right';
  color?: string;                           // Couleur du texte saisi
  backgroundColor?: string;                 // Couleur de fond du champ
  borderColor?: string;                     // Couleur de la bordure  ← v1.2.8
  labelColor?: string;                      // Couleur du label       ← v1.2.8
  fontWeight?: 'normal' | 'bold';
  fontStyle?: 'normal' | 'italic';
  styles?: Record<string, string>;          // Styles CSS inline supplémentaires

  attrs?: Record<string, any>;
}

SelectOption

interface SelectOption {
  label: string;
  value: any;
  disabled?: boolean;
  group?: string;
}

Options dynamiques depuis une API

Nouveauté v1.2.5

Pour les champs select, multiselect et radio, les options peuvent être chargées automatiquement depuis une API REST au lieu d'être définies statiquement. Il suffit d'utiliser url à la place de options.

Comment ça fonctionne

  1. Le composant détecte la propriété url sur le champ
  2. Il effectue un GET sur l'URL complète (dataUrl + url)
  3. Il mappe chaque objet de la réponse en { label, value } en utilisant displayName et displayValue
  4. Pendant le chargement, le select affiche "Chargement…" avec un spinner

Configuration dans app.config.ts

import { provideHttpClient } from '@angular/common/http';
import { provideNgxForms } from '@suntelecoms/ngx-dynamic-form';

providers: [
  provideHttpClient(),                    // requis pour les options depuis API
  provideNgxForms({
    storage: 'local',
    dataUrl: 'https://api.monprojet.com', // base URL pour les options
  }),
]

Exemples

Options depuis une API (URL relative) :

{
  key: 'ville',
  type: 'select',
  label: 'Ville',
  col: 6,
  icon: 'location_city',
  url: 'villes',           // → GET https://api.monprojet.com/villes
  displayName: 'nom',      // propriété affichée dans la liste
  displayValue: 'id',      // propriété utilisée comme valeur du formulaire
  validation: { required: true },
}

Options depuis une API (URL absolue) :

{
  key: 'compte',
  type: 'select',
  label: 'Compte',
  col: 6,
  url: 'https://autre-api.com/comptes', // URL absolue — dataUrl ignoré
  displayName: 'numero',
  displayValue: 'numero',
  validation: { required: true },
}

Multi-sélection depuis une API :

{
  key: 'categories',
  type: 'multiselect',
  label: 'Catégories',
  col: 12,
  url: 'categories',
  displayName: 'libelle',
  displayValue: 'code',
}

Réponse API attendue

L'API doit retourner un tableau JSON d'objets :

[
  { "id": 1, "nom": "Dakar" },
  { "id": 2, "nom": "Thiès" },
  { "id": 3, "nom": "Saint-Louis" }
]

Avec displayName: 'nom' et displayValue: 'id', le select affichera Dakar, Thiès, Saint-Louis et soumettra 1, 2, 3.

Résumé des propriétés

| Propriété | Type | Description | |---|---|---| | url | string | Endpoint relatif ('villes') ou absolu ('https://...'). Quand défini, options est ignoré. | | displayName | string | Propriété de l'objet API utilisée comme label affiché (défaut : 'label') | | displayValue | string | Propriété de l'objet API utilisée comme valeur du formulaire (défaut : 'value') |

Note : url et options sont mutuellement exclusifs. Si url est défini, options est ignoré.


Validation

interface FieldValidation {
  required?: boolean;
  email?: boolean;
  minLength?: number;
  maxLength?: number;
  min?: number;
  max?: number;
  pattern?: string | RegExp;
  custom?: (value: any) => string | null;
  messages?: {
    required?: string;
    email?: string;
    minLength?: string;
    maxLength?: string;
    min?: string;
    max?: string;
    pattern?: string;
  };
}

Exemples :

// Validateur avec message personnalisé
{
  key: 'email',
  type: 'email',
  label: 'E-mail',
  validation: {
    required: true,
    email: true,
    messages: {
      required: 'L\'e-mail est obligatoire.',
      email: 'Format e-mail invalide.',
    },
  },
}

// Validateur custom
{
  key: 'password',
  type: 'password',
  label: 'Mot de passe',
  validation: {
    required: true,
    custom: (v) => {
      if (!v || v.length < 8) return 'Minimum 8 caractères.';
      if (!/[A-Z]/.test(v)) return 'Au moins une majuscule.';
      return null;
    },
  },
}

Affichage conditionnel — showWhen

Un champ peut être affiché ou masqué en fonction de la valeur d'un autre champ, sans écrire de code Angular.

// Condition unique — afficher "Entreprise" seulement si accountType === 'pro'
{
  key: 'company',
  type: 'text',
  label: 'Entreprise',
  showWhen: { field: 'accountType', operator: 'equals', value: 'pro' },
}

// Conditions multiples (logique AND)
{
  key: 'discount',
  type: 'number',
  label: 'Remise (%)',
  showWhen: [
    { field: 'accountType', operator: 'equals',      value: 'pro' },
    { field: 'orderTotal',  operator: 'greaterThan', value: 500 },
  ],
}

Opérateurs disponibles

| Opérateur | Description | |---|---| | equals | Valeur exactement égale | | notEquals | Valeur différente | | contains | Contient (string ou tableau) | | greaterThan | Valeur numérique supérieure | | lessThan | Valeur numérique inférieure | | exists | Champ renseigné (non vide, non null) | | notExists | Champ vide ou null |


Icônes (Material Icons)

{ key: 'email', type: 'email', label: 'E-mail', icon: 'email', iconSuffix: 'verified' }

| Champ | Icône recommandée | |---|---| | Nom, Prénom | person, badge | | E-mail | email | | Téléphone | phone | | Mot de passe | lock | | Adresse | home, location_on | | Entreprise | business | | Prix | euro, attach_money | | Date | calendar_today | | Pièce jointe | attach_file |


Disposition en grille (col)

La propriété col détermine le nombre de colonnes sur 12 occupées par le champ.

| col | Largeur | Usage | |---|---|---| | 12 | 100% | Pleine largeur (défaut) | | 6 | 50% | Deux champs côte à côte | | 4 | 33% | Trois champs côte à côte | | 3 | 25% | Quatre champs côte à côte |

En dessous de 600 px, tous les champs passent automatiquement en pleine largeur.

Exemple avec sections :

fields: [
  { key: '_s1', type: 'heading', text: 'Informations', level: 3, col: 12 },
  { key: 'name',  type: 'text',  label: 'Nom',    col: 6 },
  { key: 'email', type: 'email', label: 'E-mail', col: 6 },
  { key: '_div',  type: 'divider', col: 12 },
  { key: '_s2', type: 'heading', text: 'Adresse', level: 3, col: 12 },
]

API du composant DynamicForm

Inputs

| Propriété | Type | Description | |---|---|---| | [config] | DynamicFormConfig | Configuration du formulaire (obligatoire) | | [initialValues] | Record<string, any> | Préremplissage des champs (mode édition) |

Outputs

| Événement | Type | Description | |---|---|---| | (formSubmit) | DynamicFormSubmitEvent | Formulaire soumis et valide | | (formChange) | Record<string, any> | Émis à chaque modification | | (formReset) | void | Formulaire réinitialisé |

API publique via @ViewChild

@ViewChild('f') formRef!: DynamicFormComponent;

// Accéder au FormGroup Angular
const fg = this.formRef.getForm();
console.log(fg.value, fg.valid);

// Mettre à jour des valeurs sans réinitialiser
this.formRef.patchValues({ name: 'Nouveau nom' });
<ngx-dynamic-form
  #f
  [config]="config"
  [initialValues]="existingData"
  (formSubmit)="onSubmit($event)"
  (formChange)="onChange($event)"
/>

Formulaires multi-étapes — Stepper

Nouveauté v1.2.0

Un formulaire stepper découpe la saisie en plusieurs étapes numérotées. L'utilisateur avance étape par étape — chaque étape est validée avant de passer à la suivante. À la fin, une étape Récapitulatif affiche toutes les valeurs saisies avec la possibilité de revenir corriger une étape.

Comment ça fonctionne

Étape 1        Étape 2        Étape 3        Récapitulatif
Identité  ──►  Adresse  ──►  Documents ──►  Vérification + Envoi
  • Navigation linéaire : impossible de passer à l'étape suivante si l'étape en cours est invalide.
  • Bouton Modifier dans le récapitulatif : permet de revenir à n'importe quelle étape.
  • Un seul formSubmit : les données de toutes les étapes sont fusionnées dans un seul objet au moment de l'envoi.

Utilisation du composant <ngx-stepper-form>

import { Component } from '@angular/core';
import { StepperFormComponent } from '@suntelecoms/ngx-dynamic-form';
import type { StepperFormConfig, DynamicFormSubmitEvent } from '@suntelecoms/ngx-dynamic-form';

@Component({
  selector: 'app-inscription',
  standalone: true,
  imports: [StepperFormComponent],
  template: `
    <ngx-stepper-form [config]="config" (formSubmit)="onSubmit($event)" />
  `,
})
export class InscriptionComponent {
  config: StepperFormConfig = {
    showSummary: true,
    submitLabel: 'Valider',
    steps: [
      {
        label: 'Identité',
        icon: 'person',
        fields: [
          { key: 'firstName', type: 'text',  label: 'Prénom', col: 6, validation: { required: true } },
          { key: 'lastName',  type: 'text',  label: 'Nom',    col: 6, validation: { required: true } },
          { key: 'email',     type: 'email', label: 'E-mail', col: 12, validation: { required: true, email: true } },
        ],
      },
      {
        label: 'Adresse',
        icon: 'home',
        fields: [
          { key: 'street', type: 'text',   label: 'Rue',         col: 12, validation: { required: true } },
          { key: 'city',   type: 'text',   label: 'Ville',       col: 6,  validation: { required: true } },
          { key: 'zip',    type: 'text',   label: 'Code postal', col: 6,  validation: { required: true } },
        ],
      },
      {
        label: 'Confirmation',
        icon: 'check_circle',
        fields: [
          { key: 'terms', type: 'checkbox', placeholder: "J'accepte les CGU", col: 12,
            validation: { required: true } },
        ],
      },
    ],
  };

  onSubmit(event: DynamicFormSubmitEvent): void {
    console.log(event.value);
  }
}

Configuration — StepperFormConfig

interface StepperFormConfig {
  steps: FormStep[];            // Liste des étapes (obligatoire)
  submitLabel?: string;         // Libellé du bouton Envoyer (défaut : "Envoyer")
  showReset?: boolean;          // Afficher Réinitialiser (défaut : false)
  resetLabel?: string;          // Libellé du bouton Reset
  linear?: boolean;             // Bloque la navigation si l'étape est invalide (défaut : true)
  orientation?: 'horizontal' | 'vertical';
  showSummary?: boolean;        // Ajoute une étape Récapitulatif à la fin (défaut : false)
  summaryLabel?: string;        // Libellé de l'étape récapitulatif (défaut : "Récapitulatif")
  cssClass?: string;
  debug?: boolean;
}

Configuration — FormStep

interface FormStep {
  label: string;                // Titre de l'étape affiché dans la barre de navigation
  icon?: string;                // Icône Material Icons (optionnelle)
  description?: string;         // Sous-titre de l'étape (optionnel)
  fields: DynamicFormField[];
}

L'étape Récapitulatif

Quand showSummary: true, une étape supplémentaire est ajoutée automatiquement à la fin du stepper. Elle affiche :

  • Toutes les étapes sous forme de sections distinctes
  • Pour chaque champ rempli : son label et sa valeur formatée
  • Un bouton Modifier par étape pour naviguer directement vers elle
  • Les types spéciaux sont formatés intelligemment :
    • checkbox → "Oui" / "Non"
    • select / radio → le label de l'option (pas la valeur brute)
    • date → format jj/mm/aaaa
    • password••••••••
    • multiselect → labels séparés par des virgules

Inputs de StepperFormComponent

| Propriété | Type | Description | |---|---|---| | [config] | StepperFormConfig | Configuration du stepper (obligatoire) | | [initialValues] | Record<string, any> | Préremplissage des champs |

Outputs de StepperFormComponent

| Événement | Type | Description | |---|---|---| | (formSubmit) | DynamicFormSubmitEvent | Toutes les étapes valides — données fusionnées | | (formChange) | Record<string, any> | Émis à chaque modification (toutes étapes) | | (stepChange) | number | Index de la nouvelle étape active |


provideNgxForms — configuration globale

provideNgxForms() est la fonction de configuration du plugin. Elle enregistre le FormConfigService et l'adaptateur de stockage dans le contexte Angular.

Stockage localStorage (défaut)

// app.config.ts
import { provideHttpClient } from '@angular/common/http';
import { provideNgxForms } from '@suntelecoms/ngx-dynamic-form';

providers: [
  provideNgxForms({ storage: 'local' }),
]

Avec options dynamiques depuis une API :

providers: [
  provideHttpClient(),
  provideNgxForms({
    storage: 'local',
    dataUrl: 'https://api.monprojet.com',
  }),
]

Stockage API REST (Spring Boot) — API ouverte

// app.config.ts
providers: [
  provideHttpClient(),
  provideNgxForms({
    storage: 'http',
    apiUrl: 'https://api.monprojet.com/api/forms',
    dataUrl: 'https://api.monprojet.com',
  }),
]

Stockage API REST — API sécurisée (Bearer token) (v1.3.0)

Si votre API exige un token JWT, ajoutez auth et enregistrez l'intercepteur fourni :

// app.config.ts
import { provideHttpClient, withInterceptors } from '@angular/common/http';
import { ngxFormsAuthInterceptor, provideNgxForms } from '@suntelecoms/ngx-dynamic-form';

providers: [
  provideHttpClient(withInterceptors([ngxFormsAuthInterceptor])),
  provideNgxForms({
    storage: 'http',
    apiUrl: 'https://api.monprojet.com/api/forms',
    dataUrl: 'https://api.monprojet.com',
    auth: {
      loginUrl: 'https://api.monprojet.com/security-service/api/login',
      username: 'admin',
      password: 'secret',
    },
  }),
]

L'intercepteur est no-op si auth n'est pas configuré. Les APIs ouvertes ne nécessitent aucune modification.

Le token est mis en cache en mémoire et renouvelé automatiquement avant expiration (expiresIn - 10s).

Options de configuration

| Option | Type | Description | |---|---|---| | storage | 'local' \| 'http' | Moteur de stockage des schémas | | apiUrl | string | (mode http uniquement) URL de base de l'API de stockage | | dataUrl | string | (optionnel) Base URL pour le chargement des options de champs | | auth.loginUrl | string | (optionnel) Endpoint de login retournant { data: { accessToken, expiresIn } } | | auth.username | string | Identifiant pour le login | | auth.password | string | Mot de passe pour le login |

Endpoints REST attendus

L'API doit exposer les endpoints suivants :

GET    /api/forms                → SavedFormSchema[]
GET    /api/forms/:id            → SavedFormSchema
GET    /api/forms/code/:code     → SavedFormSchema  (récupération par code unique)
POST   /api/forms                → SavedFormSchema  (création — l'API génère l'id UUID)
PUT    /api/forms/:id            → SavedFormSchema  (mise à jour)
DELETE /api/forms/:id            → void

FormConfigService — gestion des schémas

FormConfigService est fourni par provideNgxForms(). Il gère les schémas de formulaires avec un Signal réactif et une API Observable.

Depuis la v1.2.9, chaque schéma possède un code unique saisi par l'utilisateur (ex : "contact-form") et une date de création createdAt. La sauvegarde est séparée en create() (nouveau formulaire) et update() (formulaire existant).

import { inject } from '@angular/core';
import { FormConfigService } from '@suntelecoms/ngx-dynamic-form';

export class MonComposant {
  private formService = inject(FormConfigService);

  // Signal réactif — liste mise à jour automatiquement
  readonly schemas = this.formService.schemas; // Signal<SavedFormSchema[]>

  // Lecture synchrone par id
  getById(id: string) {
    return this.formService.get(id); // SavedFormSchema | null
  }

  // Lecture synchrone par code
  getByCode(code: string) {
    return this.formService.getByCode(code); // SavedFormSchema | null
  }

  // Création (nouveau formulaire — l'id sera généré automatiquement)
  createForm(code: string, title: string, config: AnyFormConfig) {
    this.formService.create(code, title, config).subscribe(schema => {
      console.log('Créé avec id :', schema.id);
    });
  }

  // Mise à jour (formulaire existant)
  updateForm(schema: SavedFormSchema, title: string, config: AnyFormConfig) {
    this.formService.update(schema, title, config).subscribe(updated => {
      console.log('Mis à jour :', updated.updatedAt);
    });
  }

  // Suppression
  deleteForm(id: string) {
    this.formService.delete(id).subscribe(() => console.log('Supprimé'));
  }
}

Interface SavedFormSchema

interface SavedFormSchema {
  id: string;            // UUID généré automatiquement (côté serveur ou localStorage)
  code: string;          // Code unique saisi par l'utilisateur (ex : "contact-form")
  title: string;         // Nom affiché du formulaire
  config: AnyFormConfig; // DynamicFormConfig OU StepperFormConfig
  createdAt: string;     // ISO 8601 — date de création (non modifiable)
  updatedAt: string;     // ISO 8601 — date de dernière modification
}

// AnyFormConfig = DynamicFormConfig | StepperFormConfig
// Pour distinguer les deux : 'steps' in config → c'est un StepperFormConfig

Stockage — LocalStorage vs API REST

Le plugin utilise une abstraction FormStorageAdapter qui permet de changer le backend de stockage sans toucher au reste de l'application.

Résumé des adaptateurs

| Option | Classe | Quand utiliser | |---|---|---| | storage: 'local' | LocalStorageAdapter | Développement, démo, usage hors-ligne | | storage: 'http' | HttpStorageAdapter | Production avec backend Spring Boot |

Clé localStorage

Quand storage: 'local', les données sont stockées sous :

localStorage['ngx-dynamic-form__schemas'] = [
  { id, code, title, config, createdAt, updatedAt },
  ...
]

Implémenter son propre adaptateur

import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { FormStorageAdapter, SavedFormSchema } from '@suntelecoms/ngx-dynamic-form';

@Injectable()
export class IndexedDBAdapter extends FormStorageAdapter {
  list():                                          Observable<SavedFormSchema[]>      { /* ... */ }
  get(id: string):                                 Observable<SavedFormSchema | null> { /* ... */ }
  getByCode(code: string):                         Observable<SavedFormSchema | null> { /* ... */ }
  create(schema: Omit<SavedFormSchema, 'id'>):     Observable<SavedFormSchema>        { /* ... */ }
  update(schema: SavedFormSchema):                 Observable<SavedFormSchema>        { /* ... */ }
  delete(id: string):                              Observable<void>                   { /* ... */ }
}

// app.config.ts
providers: [
  { provide: FormStorageAdapter, useClass: IndexedDBAdapter },
  FormConfigService,
]

Form Builder — éditeur visuel

Le Form Builder est une interface 3 panneaux intégrée dans le plugin. Il supporte deux modes : Formulaire (simple) et Stepper (multi-étapes).

Ajouter la route

{
  path: 'builder',
  loadComponent: () => import('@suntelecoms/ngx-dynamic-form').then(m => m.FormBuilderComponent),
}

Interface

┌──────────────────┬───────────────────────────────┬─────────────────────┐
│  Palette         │   [Formulaire] [Stepper]       │   Propriétés        │
│                  │   Schéma / Aperçu             │   (champ sélect.)   │
│  ÉTAPES (stepper)│                               │                     │
│  ① Étape 1    ✏X│  ┌────────────────────────┐   │  Label : Nom        │
│  ② Étape 2    ✏X│  │ Nom     (text)  ↑↓⎘🗑  │   │  Col : 6/12         │
│  + Ajouter       │  │ Email   (email) ↑↓⎘🗑  │   │  Icône : person     │
│                  │  └────────────────────────┘   │  Required : ✓       │
│  TYPES DE CHAMPS │                               │                     │
│  [Texte]         │  Paramètres du formulaire     │  Validation         │
│  [E-mail]        │  Code : [contact-form]        │  Options            │
│  [Select]  ...   │  Titre : [Mon formulaire]     │                     │
│                  │  Bouton Envoyer : [Envoyer]   │                     │
│  Formulaires     │  [ ] Réinitialiser            │                     │
│  sauvegardés     │  [✓] Récapitulatif (stepper)  │                     │
│                  │                               │                     │
│                  │  [Schéma] [Aperçu]            │                     │
└──────────────────┴───────────────────────────────┴─────────────────────┘

Champ Code

La topbar du Builder expose un champ Code à côté du titre. Ce code :

  • Est saisi librement par l'utilisateur (ex : "contact-form", "inscription-2024")
  • Doit être unique — il est utilisé dans l'URL de partage (/form/contact-form) et pour la récupération par getByCode()
  • Est verrouillé après la première sauvegarde (le code ne peut plus changer une fois l'identifiant attribué)

Fonctionnalités du Builder

| Action | Description | |---|---| | Ajouter un champ | Clic sur un type dans la palette | | Réordonner | Boutons ↑ ↓ sur chaque champ | | Dupliquer | Copie d'un champ avec toutes ses propriétés | | Supprimer | Icône corbeille | | Éditer | Panneau droit — label, col, icône, validation, options… | | Mode Stepper | Bascule Formulaire ↔ Stepper en haut de page | | Gérer les étapes | Créer, renommer, supprimer les étapes | | Récapitulatif | Case à cocher pour activer l'étape de synthèse | | Aperçu | Onglet Aperçu — rendu réel en temps réel | | Sauvegarder | Crée ou met à jour selon l'état courant (nouveau / existant) | | Export JSON | JSON de la config dans un modal copiable | | Export TypeScript | const config: DynamicFormConfig = { ... } ou StepperFormConfig | | Partager | Copie l'URL /form/<code> dans le presse-papiers |

Input du FormBuilderComponent

| Input | Type | Défaut | Description | |---|---|---|---| | [backRoute] | string | '/' | Route du lien "Retour" dans la topbar |


Composants de navigation

MesFormulairesComponent — <ngx-mes-formulaires>

Liste et aperçu de tous les formulaires sauvegardés (simples et stepper). Affiche le code de chaque formulaire et permet de l'ouvrir directement via son URL /form/<code>.

{
  path: 'mes-formulaires',
  loadComponent: () => import('@suntelecoms/ngx-dynamic-form').then(m => m.MesFormulairesComponent),
}

| Input | Type | Défaut | Description | |---|---|---|---| | [backRoute] | string | '/' | Lien "Retour" | | [builderRoute] | string | '/builder' | Route vers le Builder | | [formRoute] | string | '/form' | Préfixe de la route de rendu (+ /:code) |

FormLoaderComponent — <ngx-form-loader>

Charge et affiche un formulaire depuis son code unique — détecte automatiquement s'il s'agit d'un formulaire simple ou d'un stepper.

{
  path: 'form/:code',
  loadComponent: () => import('@suntelecoms/ngx-dynamic-form').then(m => m.FormLoaderComponent),
}

| Input | Type | Défaut | Description | |---|---|---|---| | [formCode] | string | — | Code direct (sans routing, optionnel) | | [backRoute] | string | '/' | Lien "Retour" | | [builderRoute] | string | '/builder' | Lien "Éditer" |


Intégration dans un composant

Approche A — formulaire simple chargé par code

import { Component, computed, inject, signal } from '@angular/core';
import { DynamicFormComponent, FormConfigService } from '@suntelecoms/ngx-dynamic-form';
import type { DynamicFormConfig, DynamicFormSubmitEvent } from '@suntelecoms/ngx-dynamic-form';

@Component({
  standalone: true,
  imports: [DynamicFormComponent],
  template: `
    @if (activeConfig(); as cfg) {
      <ngx-dynamic-form [config]="cfg" (formSubmit)="onSubmit($event)" />
    }
  `,
})
export class MonFormulaireComponent {
  private formService = inject(FormConfigService);
  selectedCode = signal('contact-form');
  activeConfig = computed(() =>
    this.formService.getByCode(this.selectedCode())?.config as DynamicFormConfig ?? null
  );
  onSubmit(event: DynamicFormSubmitEvent): void { console.log(event.value); }
}

Approche B — stepper chargé par code (détection automatique)

import { Component, computed, inject, signal } from '@angular/core';
import { DynamicFormComponent, StepperFormComponent, FormConfigService } from '@suntelecoms/ngx-dynamic-form';
import type { DynamicFormConfig, StepperFormConfig, DynamicFormSubmitEvent } from '@suntelecoms/ngx-dynamic-form';

@Component({
  standalone: true,
  imports: [DynamicFormComponent, StepperFormComponent],
  template: `
    @if (flatConfig(); as flatCfg) {
      <ngx-dynamic-form [config]="flatCfg" (formSubmit)="onSubmit($event)" />
    }
    @if (stepperConfig(); as stepperCfg) {
      <ngx-stepper-form [config]="stepperCfg" (formSubmit)="onSubmit($event)" />
    }
  `,
})
export class MonFormulaireComponent {
  private formService = inject(FormConfigService);
  selectedCode = signal('inscription-2024');

  private anyConfig = computed(() => this.formService.getByCode(this.selectedCode())?.config ?? null);

  flatConfig = computed<DynamicFormConfig | null>(() => {
    const c = this.anyConfig();
    return c && !('steps' in c) ? c as DynamicFormConfig : null;
  });

  stepperConfig = computed<StepperFormConfig | null>(() => {
    const c = this.anyConfig();
    return c && 'steps' in c ? c as StepperFormConfig : null;
  });

  onSubmit(event: DynamicFormSubmitEvent): void { console.log(event.value); }
}

Approche C — configuration TypeScript statique

import { Component } from '@angular/core';
import { DynamicFormComponent } from '@suntelecoms/ngx-dynamic-form';
import type { DynamicFormConfig, DynamicFormSubmitEvent } from '@suntelecoms/ngx-dynamic-form';

@Component({
  standalone: true,
  imports: [DynamicFormComponent],
  template: `<ngx-dynamic-form [config]="config" (formSubmit)="onSubmit($event)" />`,
})
export class ContactComponent {
  config: DynamicFormConfig = {
    submitLabel: 'Envoyer',
    fields: [
      { key: 'nom',   type: 'text',  label: 'Nom',     col: 6, validation: { required: true } },
      { key: 'email', type: 'email', label: 'E-mail',  col: 6, validation: { required: true } },
    ],
  };
  onSubmit(event: DynamicFormSubmitEvent): void { console.log(event.value); }
}

Astuce : créez le formulaire dans le Builder (/builder) → cliquez Export TypeScript → collez dans votre composant.

Approche D — avec backend API REST (Spring Boot)

Configurez provideNgxForms({ storage: 'http', apiUrl: '...' }) — tout le reste est identique.

// app.config.ts
provideNgxForms({
  storage: 'http',
  apiUrl: 'https://api.monprojet.com/api/forms',
}),
provideHttpClient(),
// Exemple Spring Boot — FormSchemaController.java
@RestController
@RequestMapping("/api/forms")
public class FormSchemaController {

  @GetMapping                      List<FormSchema> findAll()                              { ... }
  @GetMapping("/{id}")             FormSchema       findById(@PathVariable String id)      { ... }
  @GetMapping("/code/{code}")      FormSchema       findByCode(@PathVariable String code)  { ... }
  @PostMapping                     ResponseEntity<FormSchema> create(@RequestBody FormSchema s) { ... }
  @PutMapping("/{id}")             FormSchema       update(@PathVariable String id,
                                                          @RequestBody FormSchema s)       { ... }
  @DeleteMapping("/{id}")          ResponseEntity<Void> delete(@PathVariable String id)    { ... }
}

L'id est généré par le serveur (UUID.randomUUID()) lors du POST. Le client envoie uniquement le code (choisi par l'utilisateur), le title et le config.


Pages de démonstration

L'application de démonstration (ng serve) illustre le plugin sur plusieurs cas d'usage :

| Route | Description | |---|---| | / | Page d'accueil | | /inscription | Formulaire multi-sections avec showWhen | | /contact | Formulaire de contact (select, textarea, radio) | | /produit | Fiche produit — modes vue / édition / création | | /builder | Form Builder — modes Formulaire et Stepper | | /mes-formulaires | Gestionnaire — liste et aperçu de tous les formulaires | | /form/:code | Rendu par code — formulaire simple ou stepper automatiquement |


Exemples complets

Formulaire d'inscription avec showWhen

config: DynamicFormConfig = {
  submitLabel: 'Créer mon compte',
  showReset: true,
  fields: [
    { key: '_info', type: 'heading', text: 'Informations', level: 3, col: 12 },
    { key: 'firstName', type: 'text',  label: 'Prénom', col: 6, icon: 'person',
      validation: { required: true } },
    { key: 'lastName',  type: 'text',  label: 'Nom',    col: 6, icon: 'badge',
      validation: { required: true } },
    { key: 'email',     type: 'email', label: 'E-mail', col: 12, icon: 'email',
      validation: { required: true, email: true } },
    { key: '_div', type: 'divider', col: 12 },
    { key: 'accountType', type: 'radio', label: 'Type de compte', col: 12,
      defaultValue: 'personal',
      options: [
        { label: 'Particulier',   value: 'personal' },
        { label: 'Professionnel', value: 'pro' },
      ] },
    { key: 'company', type: 'text', label: 'Entreprise', col: 6, icon: 'business',
      showWhen: { field: 'accountType', operator: 'equals', value: 'pro' } },
    { key: 'siret', type: 'text', label: 'SIRET', col: 6, icon: 'tag',
      showWhen: { field: 'accountType', operator: 'equals', value: 'pro' },
      validation: { minLength: 14, maxLength: 14 } },
    { key: 'terms', type: 'checkbox',
      placeholder: "J'accepte les CGU *", col: 12,
      validation: { required: true, messages: { required: 'Vous devez accepter les CGU.' } } },
  ],
};

Formulaire stepper — processus d'inscription en 3 étapes

config: StepperFormConfig = {
  showSummary: true,
  submitLabel: 'Créer mon compte',
  steps: [
    {
      label: 'Identité',
      icon: 'person',
      fields: [
        { key: 'firstName', type: 'text',     label: 'Prénom', col: 6, validation: { required: true } },
        { key: 'lastName',  type: 'text',     label: 'Nom',    col: 6, validation: { required: true } },
        { key: 'birthDate', type: 'date',     label: 'Date de naissance', col: 6 },
        { key: 'gender',    type: 'select',   label: 'Genre', col: 6,
          options: [{ label: 'Homme', value: 'M' }, { label: 'Femme', value: 'F' }] },
      ],
    },
    {
      label: 'Coordonnées',
      icon: 'contact_mail',
      fields: [
        { key: 'email', type: 'email', label: 'E-mail', col: 12,
          validation: { required: true, email: true } },
        { key: 'phone', type: 'tel',   label: 'Téléphone', col: 6 },
        { key: 'city',  type: 'text',  label: 'Ville', col: 6 },
      ],
    },
    {
      label: 'Sécurité',
      icon: 'lock',
      fields: [
        { key: 'password', type: 'password', label: 'Mot de passe', col: 6,
          validation: { required: true, custom: v => v?.length < 8 ? 'Minimum 8 caractères' : null } },
        { key: 'terms', type: 'checkbox', placeholder: "J'accepte les CGU", col: 12,
          validation: { required: true } },
      ],
    },
  ],
};

Fiche produit avec préremplissage et mode vue/édition

mode = signal<'view' | 'edit'>('edit');
prefill = { name: 'Casque Pro X1', price: 129.99, stock: 42 };

get config(): DynamicFormConfig {
  const ro = this.mode() === 'view';
  return {
    submitLabel: 'Sauvegarder',
    fields: [
      { key: 'name',  type: 'text',   label: 'Nom',   col: 8, icon: 'inventory', readonly: ro,
        validation: { required: true } },
      { key: 'price', type: 'number', label: 'Prix',  col: 4, icon: 'euro',      readonly: ro },
      { key: 'stock', type: 'number', label: 'Stock', col: 4, icon: 'inventory_2', readonly: ro },
    ],
  };
}
<ngx-dynamic-form
  [config]="config"
  [initialValues]="prefill"
  (formSubmit)="onSubmit($event)"
/>

Personnalisation CSS

/* styles.css */
:root {
  --ngx-form-gap: 1.25rem;
  --ngx-color-border: #e2e8f0;
  --ngx-color-text: #1a202c;
  --ngx-color-hint: #718096;
}

Pour les couleurs des champs Material (outline, focus, erreur), personnalisez le thème Angular Material selon la documentation officielle.


Interfaces TypeScript

// Exports publics de @suntelecoms/ngx-dynamic-form

// Composants
export { DynamicFormComponent }     // <ngx-dynamic-form>
export { StepperFormComponent }     // <ngx-stepper-form>   ← v1.2.0
export { FormBuilderComponent }     // <ngx-form-builder>
export { MesFormulairesComponent }  // <ngx-mes-formulaires>
export { FormLoaderComponent }      // <ngx-form-loader>

// Services & Providers
export { FormConfigService }        // inject(FormConfigService)
export { FormStorageAdapter }       // classe abstraite de stockage
export { LocalStorageAdapter }      // implémentation localStorage
export { HttpStorageAdapter }       // implémentation API REST
export { FORM_API_URL }             // InjectionToken<string>
export { provideNgxForms }          // function(options) → EnvironmentProviders
export { NGX_DATA_API_URL }         // InjectionToken<string> — base URL pour les options dynamiques  ← v1.2.5
export { ngxFormsAuthInterceptor }  // HttpInterceptorFn — ajoute Authorization: Bearer  ← v1.3.0
export { NgxFormsAuthService }      // service token/cache  ← v1.3.0
export { NGX_FORMS_AUTH }           // InjectionToken<NgxFormsAuthOptions>  ← v1.3.0

// Types
export type { NgxFormsOptions }         // { storage: 'local' } | { storage: 'http', apiUrl: string }
export type { NgxFormsAuthOptions }     // { loginUrl, username, password }  ← v1.3.0
export type { AnyFormConfig }         // DynamicFormConfig | StepperFormConfig  ← v1.2.0
export type { SavedFormSchema }       // { id, code, title, config, createdAt, updatedAt }  ← v1.2.9
export type { DynamicFormConfig }
export type { DynamicFormField }
export type { DynamicFormSubmitEvent }
export type { StepperFormConfig }     // ← v1.2.0
export type { FormStep }              // ← v1.2.0
export type { FieldType }
export type { FieldValidation }
export type { FieldCondition }
export type { SelectOption }

Changelog

v1.3.1

  • Fix boucle infinie auth : NgxFormsAuthService utilise désormais HttpBackend au lieu de HttpClient pour la requête de login — l'intercepteur ngxFormsAuthInterceptor n'intercepte plus la requête de login elle-même, évitant une récursion infinie qui empêchait le chargement des formulaires

v1.3.0

  • Authentification Bearer token : nouvelle option auth dans provideNgxForms({ auth: { loginUrl, username, password } })
  • ngxFormsAuthInterceptor : intercepteur HTTP fonctionnel à ajouter dans provideHttpClient(withInterceptors([ngxFormsAuthInterceptor])) — injecte automatiquement le header Authorization: Bearer <token> sur toutes les requêtes
  • NgxFormsAuthService : service de gestion du token JWT — login automatique au premier appel, cache en mémoire, renouvellement automatique 10 secondes avant expiration
  • No-op sans auth : l'intercepteur est transparent si auth n'est pas configuré — aucune modification requise pour les APIs ouvertes
  • Compatibilité : fonctionne avec les modes storage: 'local' et storage: 'http'

v1.2.9

  • Code unique par formulaire : nouvelle propriété code dans SavedFormSchema — identifiant court saisi par l'utilisateur (ex : "contact-form"), unique dans le stockage
  • Date de création : nouvelle propriété createdAt dans SavedFormSchema — ISO 8601, non modifiable après création
  • API de sauvegarde séparée : FormConfigService.create(code, title, config) pour les nouveaux formulaires, update(schema, title, config) pour les modifications — remplace l'ancienne méthode save()
  • Récupération par code : FormConfigService.getByCode(code) et FormStorageAdapter.getByCode(code) — permet de charger un formulaire depuis son code sans connaître son id
  • Routage par code : FormLoaderComponent lit désormais le paramètre de route :code (était :id) et appelle getByCode() — l'input formCode remplace formId
  • MesFormulairesComponent : les liens vers /form/:code utilisent maintenant schema.code — affiche "Code :" à la place de "ID :" dans la liste
  • Form Builder : champ Code dans la topbar, verrouillé après la première sauvegarde — copyFormUrl() génère /form/<code> au lieu de /form/<id>
  • Endpoint REST : GET /api/forms/code/:code ajouté ; POST /api/forms pour la création (id UUID auto-généré côté serveur) ; PUT /api/forms/:id pour la mise à jour seule

v1.2.8

  • 6 nouveaux types de champs : switch (mat-slide-toggle), range (mat-slider), color (color picker + affichage hex), location (géolocalisation GPS), time, month, week, datetime-local accessibles depuis le Builder
  • Datepicker Material : le type date utilise désormais mat-datepicker (calendrier popup Material) — conversion string → Date automatique pour les valeurs préremplies
  • Géolocalisation : le type location embarque un bouton GPS qui déclenche navigator.geolocation.getCurrentPosition() et remplit le champ avec "lat,lng"
  • Curseur range : min, max, step configurables depuis le Builder — affichage de la valeur courante en temps réel
  • Couleurs bordure et label : nouvelles propriétés borderColor et labelColor sur DynamicFormField — appliquées via CSS custom properties, ciblent les composants Material (MDC) via :host[style*="..."]

v1.2.7

  • Apparence simple : 3ème variante — label fixe au-dessus, input borduré avec border-radius: 8px — sans mat-form-field. Disponible globalement ou par champ
  • Masque de saisie : propriété mask# = chiffre, autres chars = séparateurs. Présets dans le Builder (téléphone SN, international, date, heure, CB, code postal)
  • Préfixe / suffixe texte : propriétés prefix et suffix — affichés à l'intérieur du champ
  • Styling complet par champ : fontSize, textAlign, color, backgroundColor, fontWeight, fontStyle, cssClass, styles (CSS inline arbitraire)
  • Apparence globale configurable : DynamicFormConfig.appearance et .floatLabel — le Builder expose ces réglages dans "Paramètres du formulaire"

v1.2.6

  • Fix : en mode édition, les champs select/multiselect/radio avec url reçoivent correctement leur valeur initiale même si l'API retourne un objet imbriqué
  • Fix appliqué aussi dans StepperFormComponent

v1.2.5

  • Options dynamiques depuis une API pour les champs select, multiselect, radio : propriétés url, displayName, displayValue
  • Token NGX_DATA_API_URL et option dataUrl dans provideNgxForms()
  • Form Builder : toggle Statiques / Depuis une API dans le panneau Options
  • Spinner de chargement dans le dropdown pendant le fetch des options

v1.2.0

  • Ajout du composant <ngx-stepper-form> pour les formulaires multi-étapes
  • Étape Récapitulatif automatique (showSummary: true) avec boutons Modifier par étape
  • Mode Stepper dans le Form Builder
  • AnyFormConfig = DynamicFormConfig | StepperFormConfig
  • MesFormulairesComponent et FormLoaderComponent détectent et rendent les deux types

v1.1.0

  • Grille 12 colonnes (col)
  • Form Builder, MesFormulaires et FormLoader intégrés dans le plugin
  • Système de stockage abstrait (localStorage / API REST)
  • FormConfigService Signal-based

Auteur

Amadou Diallo@amepro 📧 [email protected]

Développé dans le cadre du projet SUNTELECOMS — Plugin Angular 19 standalone.


MIT License — Libre d'utilisation, de modification et de distribution.