85 votes

Division d'un formulaire en plusieurs composants avec validation

Je veux créer un seul grand formulaire avec angular 2. Mais je veux créer ce formulaire avec plusieurs composants comme le montre l'exemple suivant.

Composant de l'application

<form novalidate #form1="ngForm" [formGroup]="myForm">
<div>
    <address></address>
</div>
<div>
    <input type="text" ngModel required/>
</div>

<input type="submit" [disabled]="!form1.form.valid" > </form>

Composant de l'adresse

<div>
<input type="text" ngModel required/> </div>

Lorsque j'utilise le code ci-dessus, il est visible dans le navigateur comme je le souhaitais, mais le bouton d'envoi n'est pas désactivé lorsque je supprime le texte dans le composant d'adresse.
Mais le bouton a été désactivé correctement lorsque j'ai supprimé le texte dans la zone de saisie du composant d'application.

0 votes

À quoi ressemble votre composant d'adresse ? Est-ce un ControlValueAccessor ?

0 votes

Juste un simple composant sans rien dans le corps de la classe

0 votes

Essayez d'ajouter ceci au bas du modèle : {{ form1.value | json }} Et voyez si cela contient les deux éléments d'entrée ou un seul. Je sais qu'un formulaire ne peut pas être divisé en composants chargés avec le routeur. Il est également possible qu'il ne puisse pas non plus "trouver" les éléments dans un composant imbriqué.

130voto

AJT_82 Points 30605

J'utiliserais un formulaire réactif qui fonctionne très bien, et pour ce qui est de votre commentaire :

Existe-t-il un autre exemple simple pour celui-ci ? Peut-être le même exemple sans les boucles

Je peux vous donner un exemple. Tout ce que vous avez à faire, c'est d'imbriquer un FormGroup et le transmettre à l'enfant.

Disons que votre formulaire ressemble à ceci, et vous voulez passer address formgroup à l'enfant :

ngOnInit() {
  this.myForm = this.fb.group({
    name: [''],
    address: this.fb.group({ // create nested formgroup to pass to child
      street: [''],
      zip: ['']
    })
  })
}

Ensuite, dans votre parent, il suffit de passer le groupe de formulaires imbriqués :

<address [address]="myForm.get('address')"></address>

Dans votre enfant, utilisez @Input pour le groupe de formulaires imbriqués :

@Input() address: FormGroup;

Et dans votre modèle, utilisez [formGroup] :

<div [formGroup]="address">
  <input formControlName="street">
  <input formControlName="zip">
</div>

Si vous ne voulez pas créer un groupe de formulaires imbriqués, vous n'avez pas besoin de le faire, vous pouvez simplement passer le formulaire parent à l'enfant, donc si votre formulaire ressemble à :

this.myForm = this.fb.group({
  name: [''],
  street: [''],
  zip: ['']
})

vous pouvez passer les contrôles que vous voulez. En utilisant le même exemple que ci-dessus, nous voudrions seulement afficher street et zip le composant enfant reste le même, mais la balise enfant dans le modèle ressemblerait alors à ceci :

<address [address]="myForm"></address>

Voici un

Démo de la première option, voici la seconde Démo

Plus d'informations ici à propos des formulaires imbriqués basés sur des modèles.

0 votes

Cette méthode vous obligerait à dupliquer le même FormGroup de l'enfant dans le parent. Dans votre exemple ci-dessus, le parent déclare adresse avec des champs rue n zip cependant, le composant d'adresse a également le même Groupe de formulaires (c'est-à-dire la rue et le code postal), à moins que je ne comprenne mal votre solution. S'il y a de nombreux niveaux d'imbrication et que les formulaires sont complexes, la duplication du code de l'enfant dans le parent devient désordonnée. L'approche pour l'éviter est de passer le formulaire parent au composant enfant, je ne suis pas un expert en la matière :( j'apprends encore.

0 votes

Comment puis-je diviser le composant sans créer un formGroup pour l'adresse ?

1 votes

@RenilBabu Vous pouvez passer le formulaire entier au composant enfant.

20voto

Jeremy Benks Points 776

Il existe également un moyen de le faire dans les formulaires pilotés par des modèles. ngModel crée automatiquement un formulaire distinct sur chaque composant, mais vous pouvez injecter le formulaire du composant parent en ajoutant ceci à votre composant :

@Component({
viewProviders: [{ provide: ControlContainer, useExisting: NgForm}]
}) export class ChildComponent

Vous devez cependant vous assurer que chaque entrée a un nom unique. Ainsi, si vous utilisez *ngFor pour appeler votre composant enfant, vous devez mettre l'index (ou tout autre identifiant unique) dans le nom, par exemple :

[name]="'address_' + i"

Si vous souhaitez structurer votre formulaire en FormGroups, vous utilisez les fonctions ngModelGroup et

viewProviders: [{ provide: ControlContainer, useExisting: NgModelGroup }]

au lieu de ngForm et ajoutez [ngModelGroup]="yourNameHere" à certains de vos composants enfants contenant des balises html.

0 votes

Cela fonctionne mais je n'arrive pas à comprendre comment mettre en place des tests en utilisant viewProvider. Pourriez-vous me montrer un exemple de test ?

0 votes

Je n'ai pas encore utilisé de tests avec des viewProviders, donc j'ai peur de ne pas pouvoir vous aider. Toutefois, si vous trouvez une solution, ce serait formidable si vous la partagiez !

10voto

Ján Halaša Points 4672

D'après mon expérience, ce type de composition de champs de formulaire est difficile à réaliser avec des formulaires pilotés par des modèles. Les champs intégrés dans votre composant d'adresse ne sont pas enregistrés dans le formulaire (objet NgForm.controls), ils ne sont donc pas pris en compte lors de la validation du formulaire.

  • Vous pouvez créer un composant ControlValueAccessor (qui accepte l'attribut ngModel) avec toutes les validations, mais il est alors difficile d'afficher les erreurs de validation et de propager les changements (l'adresse est considérée comme un champ de formulaire unique avec une valeur complexe).
  • Vous pourriez probablement passer la référence du formulaire dans le composant Adresse et y enregistrer vos contrôles internes, mais je n'ai pas essayé et cela semble être une approche étrange (je ne l'ai vue nulle part).
  • Vous pouvez passer à des formulaires réactifs (au lieu d'être pilotés par des modèles), passer un objet de groupe de formulaires (représentant une adresse) dans le composant Adresse, en conservant la validation dans votre définition de formulaire. Vous pouvez voir un exemple ici https://scotch.io/tutorials/how-to-build-nested-model-driven-forms-in-angular-2

0 votes

Merci Jan, j'ai regardé ce lien avant de poster cette question. Mais je n'arrive pas à comprendre clairement ce qui se passe car il est combiné avec une boucle *ngFor. Existe-t-il un autre exemple simple pour celui-ci ? Peut-être le même exemple sans les boucles.

1 votes

Tous les exemples que j'ai trouvés utilisent des tableaux et *ngFor, puisque c'est probablement le cas le plus courant de réutilisation des parties de formulaire. Mais je pense que cela n'affecte pas la solution - il suffit de ne pas indexer le groupe de formulaires que vous voulez obtenir dans le composant Adresse et de sauter les parties formArray.

7voto

Maczaj Srtgth Points 161

J'aimerais vous faire part d'une approche qui a fait l'affaire dans mon cas. J'ai créé la directive suivante :

import { Directive } from '@angular/core';
import { ControlContainer, NgForm } from '@angular/forms';

@Directive({
  selector: '[appUseParentForm]',
  providers: [
    {
      provide: ControlContainer,
      useFactory: function (form: NgForm) {
        return form;
      },
      deps: [NgForm]
    }
  ]
})
export class UseParentFormDirective {
}

Maintenant, si vous utilisez cette directive sur un composant enfant, par exemple :

<address app-use-parent-form></address>

Les contrôles de AddressComponent seront ajoutés au formulaire 1. Par conséquent, la validité du formulaire dépendra également de l'état des contrôles du composant enfant.

Vérifié uniquement avec Angular 6

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X