Разделение формы на несколько компонентов с проверкой

Я хочу создать одну большую форму с помощью angular 2. Но я хочу создать эту форму с несколькими компонентами, как показано в следующем примере.

Компонент приложения

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

Адресный компонент

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

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


person Lasitha Yapa    schedule 07.04.2017    source источник
comment
Как выглядит ваш адресный компонент? Это ControlValueAccessor?   -  person Ján Halaša    schedule 07.04.2017
comment
Просто простой компонент без чего-либо в теле класса   -  person Lasitha Yapa    schedule 07.04.2017
comment
Попробуйте добавить это в конец шаблона: {{form1.value | json}} И посмотрите, содержит ли он оба входных элемента или только один. Я знаю, что форму нельзя разбить на компоненты, загруженные маршрутизатором. Также возможно, что он не может найти элементы во вложенном компоненте.   -  person DeborahK    schedule 07.04.2017
comment
@DeborahK Да, правильные элементы из адресного компонента не показаны в json. Есть ли способ добиться этого?   -  person Lasitha Yapa    schedule 07.04.2017
comment
Хотя ответ AJT82 отличный и очень полезный, это решение с другим подходом, чем просили. Я предлагаю решение, как создать форму для нескольких компонентов, здесь: (ниже).   -  person Jeremy Benks    schedule 14.01.2019


Ответы (4)


Я бы использовал реактивную форму, которая работает довольно хорошо, а что касается вашего комментария:

Есть другой простой пример для этого? Может быть, тот же пример без циклов

Я могу привести вам пример. Все, что вам нужно сделать, это вложить FormGroup и передать его дочернему элементу.

Допустим, ваша форма выглядит так, и вы хотите передать address formgroup дочернему элементу:

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

Затем в вашем родителе просто передайте вложенную группу форм:

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

У вашего ребенка используйте @Input для вложенной группы форм:

@Input() address: FormGroup;

И в вашем шаблоне используйте [formGroup]:

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

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

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

вы можете передать любые элементы управления, которые захотите. Используя тот же пример, что и выше, мы хотели бы показать только street и zip, дочерний компонент останется таким же, но дочерний тег в шаблоне будет выглядеть так:

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

Вот

Демо из первый вариант, вот второй Демо

Дополнительная информация здесь о вложенных формах на основе моделей.

person AJT82    schedule 07.04.2017
comment
Этот метод потребует от вас дублирования того же определения FormGroup из дочернего элемента в родительском. В приведенном выше примере родитель объявляет адрес с полями street n zip, однако компонент адреса также имеет такое же определение FormGroup (т. Е. Street и zip), если я не понимаю ваше решение. Если существует много уровней вложенности и формы сложны, то это дублирование дочернего кода в родительский становится беспорядочным. Подход, позволяющий избежать этого, - передать родительскую форму дочернему компоненту, я не эксперт в этом :( все еще учусь. - person Raf; 29.09.2017
comment
Как я могу разделить компонент, не создавая formGroup для адреса - person Renil Babu; 22.11.2017
comment
@RenilBabu Вы можете передать всю форму дочернему компоненту. - person AJT82; 22.11.2017
comment
Это не работает ... Выдает ошибку, чтобы вместо этого создать formGroup и использовать ее в дочернем элементе - person Renil Babu; 24.11.2017
comment
@RenilBabu, это должно сработать, не могли бы вы создать демо для этого, и я был бы рад взглянуть :) - person AJT82; 24.11.2017
comment
Привет, @Alex, я только что разместил вопрос здесь stackoverflow.com/questions/48843445/ относительно моих проблем после приведенного выше примера. Если у вас будет время, не могли бы вы взглянуть? - person David; 17.02.2018
comment
Привет @ AJT82 Большое спасибо за подробное объяснение вместе с демонстрацией трески :) - person Vaibhav Miniyar; 02.04.2020

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

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

Однако вы должны убедиться, что каждый вход имеет уникальное имя. Поэтому, если вы используете * ngFor для вызова дочернего компонента, вам нужно поместить индекс (или любой другой уникальный идентификатор) в имя, например:

[name]="'address_' + i"

Если вы хотите структурировать свою форму в FormGroups, вы используете ngModelGroup и

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

вместо ngForm и добавьте [ngModelGroup]="yourNameHere" к некоторым из ваших дочерних компонентов html, содержащим теги.

person Jeremy Benks    schedule 02.11.2018
comment
Это работает, но я не могу понять, как настроить тесты при использовании viewProvider. Не могли бы вы показать тестовый пример? - person Jared Christensen; 21.01.2020
comment
Я еще не использовал тесты с viewProviders, поэтому боюсь, что не могу вам помочь. Однако, если вы это поймете, будет здорово, если вы поделитесь этим! - person Jeremy Benks; 22.01.2020

По моему опыту, такую ​​композицию полей формы сложно реализовать с помощью шаблонных форм. Поля, встроенные в ваш компонент адреса, не регистрируются в форме (объект NgForm.controls), поэтому они не учитываются при проверке формы.

  • Вы можете создать компонент ControlValueAccessor (который принимает атрибут ngModel) со всеми проверками, но тогда будет сложно отображать ошибки проверки и распространять изменения (адрес рассматривается как одно поле формы со сложным значением).
  • Вероятно, вы могли бы передать ссылку на форму в компонент Address и зарегистрировать в нем свои внутренние элементы управления, но я не пробовал этого и кажется странным подходом (я его нигде не видел).
  • Вы можете переключиться на реактивные формы (вместо шаблонов), передать объект группы форм (представляющий адрес) в компонент Address, сохранив валидацию в определении формы. Вы можете увидеть здесь пример https://scotch.io/tutorials/how-to-build-nested-model-driven-forms-in-angular-2
person Ján Halaša    schedule 07.04.2017
comment
Спасибо, Ян. Я просмотрел эту ссылку перед тем, как опубликовать этот вопрос. Но я не могу четко понять, что происходит, поскольку это сочетается с циклом * ngFor. Есть ли другой простой пример для этого? Может быть, тот же пример без петель. - person Lasitha Yapa; 07.04.2017
comment
Во всех найденных мною примерах используются массивы и * ngFor, поскольку это, вероятно, наиболее распространенный вариант повторного использования частей формы. Но я думаю, что это не влияет на решение - просто не индексируйте группу форм, которую вы хотите получить, в компонент Address и пропускайте части formArray. - person Ján Halaša; 07.04.2017

Хочу поделиться подходом, который сработал в моем случае. Я создал следующую директиву:

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

Теперь, если вы используете эту директиву для дочернего компонента, например:

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

элементы управления из AddressComponent будут добавлены в form1. В результате валидность формы также будет зависеть от состояния элементов управления внутри дочернего компонента.

Проверено только с Angular 6

person Maczaj Srtgth    schedule 14.06.2018