web-dev-qa-db-fra.com

Formes angulaires 2 imbriquées avec composants enfants et validation

J'essaie de créer un formulaire imbriqué avec validation dans Angular 2, j'ai vu des publications et suivi la documentation, mais j'ai vraiment du mal à espérer que vous puissiez me diriger dans la bonne direction.

Ce que j'essaie de réaliser, c'est d'avoir un formulaire validé avec plusieurs composants enfants. Ces composants enfants sont un peu complexes, certains d’entre eux ont plus d’éléments enfants, mais je crois que nous pouvons nous attaquer au problème de la présence d’un parent et d’un enfant.

Qu'est-ce que j'essaie d'accomplir

Avoir un formulaire qui fonctionne comme ceci:

<div [formGroup]="userForm" novalidate>
    <div>
        <label>User Id</label>
        <input formControlName="userId">
    </div>
    <div>
        <label>Dummy</label>
        <input formControlName="dummyInput">
    </div>
</div>

Cela nécessite d'avoir un cours comme celui-ci:

private userForm: FormGroup;
constructor(private fb: FormBuilder){
    this.createForm();
}
private createForm(): void{
    this.userForm = this.fb.group({
        userId: ["", Validators.required],
        dummyInput: "", Validators.required]
    });
}

Cela fonctionne comme prévu, mais je souhaite maintenant découpler le code et placer la fonctionnalité "dummyInput" dans un composant distinct et distinct. C'est là que je me perds. C'est ce que j'ai essayé, je pense que je ne suis pas loin d'obtenir la réponse, mais je suis vraiment à court d'idées, je suis relativement nouveau dans la scène:

parent.component.html

<div [formGroup]="userForm" novalidate>
    <div>
        <label>User Id</label>
        <input formControlName="userId">
    </div>
    <div>
        <dummy></dummy>
    </div>
</div>

parent.component.ts

private createForm(): void{
    this.userForm = this.fb.group({
    userId: ["", Validators.required],
    dummy: this.fb.group({
        dummyInput: ["", Validators.required]
    })
});

children.component.html

<div [formGroup]="dummyGroup">
    <label>Dummy Input: </label>
    <input formControlName="dummyInput">
</div>

children.component.ts

private dummyGroup: FormGroup;

Je sais que quelque chose ne va pas avec le code, mais je suis vraiment dans un obstacle. Toute aide serait appréciée.

Merci.

13
Mese

L'idée principale est que vous devez traiter formGroup et formControls comme des variables, principalement des objets et des tableaux javascript.

Donc, je vais mettre un peu de code pour faire mon point. Le code ci-dessous est un peu comme ce que vous avez. Le formulaire est construit dynamiquement, mais il est divisé en sections, chaque section contenant sa part de champs et d'étiquettes.

Le code HTML est sauvegardé par les classes TypeScript. Ceux-ci ne sont pas ici car ils n'ont pas beaucoup de spécial. Seuls les FormSchemaUI, FormSectionUI et FormFieldUI sont importants.

Traitez chaque morceau de code comme son propre fichier.

Veuillez également noter que formSchema: FormSchema est un objet JSON que je reçois d’un service. Toutes les propriétés des classes d'interface utilisateur que vous ne voyez pas définies sont héritées de leurs classes de données de base. Celles-ci ne sont pas présentées ici .. La hiérarchie est la suivante: FormSchema contient plusieurs sections. Une section contient plusieurs champs.

<form (ngSubmit)="onSubmit()" #ciRegisterForm="ngForm" [formGroup]="formSchemaUI.MainFormGroup">
    <button kendoButton (click)="onSubmit(ciRegisterForm)" [disabled]="!canSubmit()"> Register {{registerPageName}} </button>
    <br /><br />
    <app-ci-register-section *ngFor="let sectionUI of formSchemaUI.SectionsUI" [sectionUI]="sectionUI">
    </app-ci-register-section>
    <button kendoButton (click)="onSubmit(ciRegisterForm)" [disabled]="!canSubmit()"> Register {{registerPageName}} </button>
</form>

===========================================

<div class="row" [formGroup]="sectionUI.MainFormGroup">
    <div class="col-md-12  col-lg-12" [formGroupName]="sectionUI.SectionDisplayId">
        <fieldset class="section-border">
            <legend class="section-border">{{sectionUI.Title}}</legend>
            <ng-container *ngFor='let fieldUI of sectionUI.FieldsUI; let i=index; let even = even;'>
                <div class="row" *ngIf="even">
                    <ng-container>
                        <div class="col-md-6  col-lg-6" app-ci-field-label-Tuple [fieldUI]="fieldUI">

                        </div>
                    </ng-container>
                    <ng-container *ngIf="sectionUI.Fields[i+1]">
                        <div class="col-md-6  col-lg-6" app-ci-field-label-Tuple [fieldUI]="sectionUI.FieldsUI[i+1]">

                        </div>
                    </ng-container>
                </div>
            </ng-container>           
        </fieldset>
    </div>
</div>

===========================================

{{fieldUI.Label}}

===========================================

<ng-container>
    <div class="row">
        <div class="col-md-4 col-lg-4 text-right">
            <label for="{{fieldUI.FieldDisplayId}}"> {{fieldUI.Label}} </label>
        </div>
        <div class="col-md-8 col-lg-8">
            <div app-ci-field-edit [fieldUI]="fieldUI" ></div>
        </div>
    </div>       
</ng-container>

===========================================

<ng-container [formGroup]="fieldUI.ParentSectionFormGroup">    
    <ng-container *ngIf="fieldUI.isEnabled">         
        <ng-container [ngSwitch]="fieldUI.ColumnType">            
            <input *ngSwitchCase="'HIDDEN'" type="hidden" id="{{fieldUI.FieldDisplayId}}" [value]="fieldUI.Value" />
            <ci-field-textbox *ngSwitchDefault
                              [fieldUI]="fieldUI"
                              (valueChange)="onValueChange($event)"
                              class="fullWidth" style="width:100%">
            </ci-field-textbox>
        </ng-container>       
    </ng-container>
</ng-container>

===========================================

export class FormSchemaUI extends FormSchema { 

    SectionsUI: Array<FormSectionUI>;
    MainFormGroup: FormGroup;

    static fromFormSchemaData(formSchema: FormSchema): FormSchemaUI {
        let formSchemaUI = new FormSchemaUI(formSchema);
        formSchemaUI.SectionsUI = new Array<FormSectionUI>();
        formSchemaUI.Sections.forEach(section => {
            let formSectionUI = FormSectionUI.fromFormSectionData(section);

            formSchemaUI.SectionsUI.Push(formSectionUI);
        });
        formSchemaUI.MainFormGroup = FormSchemaUI.buildMainFormGroup(formSchemaUI);        
        return formSchemaUI;
    }

    static buildMainFormGroup(formSchemaUI: FormSchemaUI): FormGroup {
        let obj = {};
        formSchemaUI.SectionsUI.forEach(sectionUI => {
            obj[sectionUI.SectionDisplayId] = sectionUI.SectionFormGroup;
        });
        let sectionFormGroup = new FormGroup(obj);
        return sectionFormGroup;
    }
}

===========================================

export class FormSectionUI extends FormSection {

    constructor(formSection: FormSection) {        
        this.SectionDisplayId = 'section' + this.SectionId.toString();
    }

    SectionDisplayId: string;
    FieldsUI: Array<FormFieldUI>;
    HiddenFieldsUI: Array<FormFieldUI>;
    SectionFormGroup: FormGroup;
    MainFormGroup: FormGroup;
    ParentFormSchemaUI: FormSchemaUI;

    static fromFormSectionData(formSection: FormSection): FormSectionUI {
        let formSectionUI = new FormSectionUI(formSection);
        formSectionUI.FieldsUI = new Array<FormFieldUI>();
        formSectionUI.HiddenFieldsUI = new Array<FormFieldUI>();
        formSectionUI.Fields.forEach(field => {
            let fieldUI = FormFieldUI.fromFormFieldData(field);
            if (fieldUI.ColumnType != 'HIDDEN')
                formSectionUI.FieldsUI.Push(fieldUI);
            else formSectionUI.HiddenFieldsUI.Push(fieldUI);
        });
        formSectionUI.SectionFormGroup = FormSectionUI.buildFormSectionFormGroup(formSectionUI);
        return formSectionUI;
    }

    static buildFormSectionFormGroup(formSectionUI: FormSectionUI): FormGroup {
        let obj = {};
        formSectionUI.FieldsUI.forEach(fieldUI => {
            obj[fieldUI.FieldDisplayId] = fieldUI.FieldFormControl;
        });
        let sectionFormGroup = new FormGroup(obj);
        return sectionFormGroup;
    }
}

===========================================

export class FormFieldUI extends FormField {    

    constructor(formField: FormField) {
    super();
    this.FieldDisplayId = 'field' + this.FieldId.toString();       

    this.ListItems = new Array<SelectListItem>();        
   }

    public FieldDisplayId: string;

    public FieldFormControl: FormControl;
    public ParentSectionFormGroup: FormGroup;
    public MainFormGroup: FormGroup;
    public ParentFormSectionUI: FormSectionUI;  

    public ValueChange: EventEmitter<any> = new EventEmitter<any>();    

    static buildFormControl(formFieldUI:FormFieldUI): FormControl {
        let nullValidator = Validators.nullValidator;

        let fieldKey: string = formFieldUI.FieldDisplayId; 
        let fieldValue: any;
        switch (formFieldUI.ColumnType) {            
            default:
                fieldValue = formFieldUI.Value;
                break;
        }
        let isDisabled = !formFieldUI.IsEnabled;
        let validatorsArray: ValidatorFn[] = new Array<ValidatorFn>();
        let asyncValidatorsArray: AsyncValidatorFn[] = new Array<AsyncValidatorFn>();

        let formControl = new FormControl({ value: fieldValue, disabled: isDisabled }, validatorsArray, asyncValidatorsArray);
        return formControl;
    }
}
1
Dragos Durlut

vous pouvez ajouter une entrée dans votre composant enfants pour lui transmettre le groupe de formulaires. et utiliser FormGroupName pour transmettre le nom de votre groupe de formulaires :)

children.component.ts

@Input('group');
private dummyGroup: FormGroup;

parent.component.html

<div [formGroup]="userForm" novalidate>
    <div>
        <label>User Id</label>
        <input formControlName="userId">
    </div>
    <div formGroupName="dummy">
        <dummy [group]="userForm.controls['dummy']"></dummy>
    </div>
</div>
10

Je ne vais pas mentir, je ne sais pas comment je n'ai pas trouvé cet article plus tôt.

Angular 2: Formulaire contenant un composant enfant

La solution consiste à lier le composant enfants au même formulaire formGroup en transmettant le formGroup du parent aux enfants en tant qu'entrée.

Si quelqu'un partage un morceau de code pour résoudre le problème d'une autre manière, je l'accepterai volontiers.

3
Mese

Pour obtenir une référence au formulaire parent, utilisez simplement ceci (peut-être pas disponible dans Angular 2. Je l'ai testé avec Angular 6):

TS

import {
   FormGroup,
   ControlContainer,
   FormGroupDirective,
} from "@angular/forms";

@Component({
  selector: "app-leveltwo",
  templateUrl: "./leveltwo.component.html",
  styleUrls: ["./leveltwo.component.sass"],
  viewProviders: [
    {
      provide: ControlContainer,
      useExisting: FormGroupDirective
    }
  ]
})
export class NestedLevelComponent implements OnInit {
  //form: FormGroup;

  constructor(private parent: FormGroupDirective) {
     //this.form = form;
  }
}

HTML

<input type="text" formControlName="test" />
1
blacksheep_2011
import { Directive } from '@angular/core';
import { ControlContainer, NgForm } from '../../../node_modules/@angular/forms';

@Directive({
  selector: '[ParentProvider]',
  providers: [
    {
    provide: ControlContainer,
    useFactory: function (form: NgForm) {
      return form;
    },
    deps: [NgForm]
    }`enter code here`
  ]
})
export class ParentProviderDirective {

  constructor() { }

}
<div ParentProvider >
  for child
</div>
0

Une alternative à FormGroupDirective (comme décrit dans @ blacksheep's answer ) consiste à utiliser ControlContainer comme ceci:

import { FormGroup, ControlContainer } from "@angular/forms";

export class ChildComponent implements OnInit {

  formGroup: FormGroup;

  constructor(private controlContainer: ControlContainer) {}

  ngOnInit() {
    this.formGroup = <FormGroup>this.controlContainer.control;
  }

La formGroup peut être définie dans le parent direct ou plus haut (parent du parent, par exemple). Cela permet de passer un groupe à partir de divers composants imbriqués, sans qu'une chaîne de @Input()s soit nécessaire pour faire passer le formGroup. Dans n'importe quel parent, définissez formGroup pour le rendre disponible via ControlContainer dans l'enfant:

<... [formGroup]="myFormGroup">
0
Markus Pscheidt