Angular 2 вложенных формы с дочерними компонентами и проверка

Я пытаюсь получить вложенную форму с проверкой в ​​ Angular 2, я видел сообщения и следил за документацией, но я действительно борется, надеюсь, что вы можете указать мне в правильном направлении.

То, что я пытаюсь достичь, - это иметь проверенную форму с несколькими дочерними компонентами. Эти компоненты для детей немного сложны, некоторые из них имеют больше компонентов для детей, но, ради вопроса, я думаю, что мы можем атаковать проблему с родителем и детьми.

Что я пытаюсь выполнить

Имея форму, которая работает следующим образом:

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

Для этого требуется наличие такого класса:

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

Это работает так, как ожидалось, но теперь я хочу развязать код и поместить функцию "dummyInput" в отдельный, другой компонент. Здесь я теряюсь. Это то, что я пробовал, я думаю, что я не далеко от ответа, но у меня действительно нет идей, я довольно новичок в этой сцене:

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;

Я знаю, что с кодом что-то не так, но я действительно в блокпосте. Любая помощь будет оценена.

Спасибо.

Ответ 1

Основная идея заключается в том, что вы должны рассматривать formGroup и formControls как переменные, в основном объекты javascript и массивы.

Поэтому я добавлю код, чтобы понять. Ниже приведен код, похожий на то, что у вас есть. Форма построена динамически, просто разбивается на разделы, каждая секция содержит свою долю полей и меток.

HTML подкрепляется классами машинописного текста. Их здесь нет, так как они не имеют особого значения. Важны только FormSchemaUI, FormSectionUI и FormFieldUI.

Рассматривайте каждую часть кода как свой собственный файл.

Также обратите внимание, что formSchema: FormSchema - это объект JSON, который я получаю от службы. Любые свойства классов пользовательского интерфейса, которые вы не видите, наследуются от их базовых кластеров данных. Они не представлены здесь. Иерархия: FormSchema содержит несколько разделов. Раздел содержит несколько полей.

<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;
    }
}

Ответ 2

вы можете добавить элемент ввода в свой дочерний компонент, чтобы передать ему FormGroup. и используйте FormGroupName для передачи имени вашей FormGroup:)

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>

Ответ 3

Не собираюсь врать, не знаю, как я не нашел этот пост раньше.

Angular 2: Форма, содержащая дочерний компонент

Решение состоит в том, чтобы привязать дочерний компонент к той же группе форм, передав formGroup из родительского элемента в дочерний элемент как вход.

Если кто-то разделяет кусок кода, чтобы решить проблему другим способом, я с радостью приму это.

Ответ 4

Чтобы получить ссылку на родительскую форму, просто используйте это (возможно, не доступно в Angular 2. Я тестировал его с помощью 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" />

Ответ 5

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>

Ответ 6

Альтернативой FormGroupDirective (как описано в ответе @blacksheep) является использование ControlContainer следующим образом:

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

export class ChildComponent implements OnInit {

  formGroup: FormGroup;

  constructor(private controlContainer: ControlContainer) {}

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

formGroup можно задать в прямом родителе или далее вверх (родительский родитель, например). Это позволяет передавать из группы по различным вложенным компонентам, без необходимости @Input() цепочку @Input() чтобы передать formGroup. В любом родительском наборе formGroup чтобы сделать его доступным через ControlContainer в child:

<... [formGroup]="myFormGroup">