假设我从angular2 app生成的html看起来像这样:
- <app>
- <form [formGroup]="myForm" (ngSubmit)="onSubmit(myForm.value)">
- <panel-component>
- <mid-component>
- <inner-component-with-inputs>
- <input/>
- <inner-component-with-inputs>
- <mid-component>
- </panel-component>
- <panel-component>
- <mid-component>
- <inner-component-with-inputs>
- <input/>
- <inner-component-with-inputs>
- <mid-component>
- </panel-component>
- <!-- many many many fields -->
- <button type="submit">Submit</button>
- </form>
- </app>
如何设置外部< form>以这种方式我可以在提交时验证所有内部输入?我是否必须通过@Input()将myForm从面板组件一直传递到内部组件与输入?或者还有其他方式吗?
在我的应用程序中,我有非常大的形式,有多个面板,子面板,标签,模态等.我需要能够在提交时立即验证它.
互联网上的所有教程和资源仅涉及跨越一个组件/模板的表单.
解决方法
当涉及父/子关系时,您将在整个Angular源代码中看到的常见模式是父类型,将自身添加为自身的提供者.这样做是允许子组件注入父组件.由于
hierarchical DI,在组件树中只会有一个父组件的实例.下面是一个可能看起来像的例子
- export abstract class FormControlContainer {
- abstract addControl(name: string,control: FormControl): void;
- abstract removeControl(name: string): void;
- }
- export const formGroupContainerProvider: any = {
- provide: FormControlContainer,useExisting: forwardRef(() => NestedFormComponentsComponent)
- };
- @Component({
- selector: 'nested-form-components',template: `
- ...
- `,directives: [REACTIVE_FORM_DIRECTIVES,ChildComponent],providers: [formGroupContainerProvider]
- })
- export class ParentComponent implements FormControlContainer {
- form: FormGroup = new FormGroup({});
- addControl(name: string,control: FormControl) {
- this.form.addControl(name,control);
- }
- removeControl(name: string) {
- this.form.removeControl(name);
- }
- }
一些说明:
>我们使用接口/抽象父(FormControlContainer)有几个原因
>它将ParentComponent与ChildComponent分离.孩子不需要知道关于特定ParentComponent的任何信息.所有它知道的是FormControlContainer和合同.
>我们只通过接口契约在ParentComponent上公开需要的方法.
>我们只将ParentComponent宣传为FormControlContainer,因此后者是我们将注入的内容.
>我们以formControlContainerProvider的形式创建提供程序,然后将该提供程序添加到ParentComponent.由于分层DI,现在所有孩子都可以访问父母.
>如果您不熟悉forwardRef,this is a great article
现在,你可以做孩子
- @Component({
- selector: 'child-component',directives: [REACTIVE_FORM_DIRECTIVES]
- })
- export class ChildComponent implements OnDestroy {
- firstName: FormControl;
- lastName: FormControl;
- constructor(private _parent: FormControlContainer) {
- this.firstName = new FormControl('',Validators.required);
- this.lastName = new FormControl('',Validators.required);
- this._parent.addControl('firstName',this.firstName);
- this._parent.addControl('lastName',this.lastName);
- }
- ngOnDestroy() {
- this._parent.removeControl('firstName');
- this._parent.removeControl('lastName');
- }
- }
IMO,这比通过@Inputs传递FormGroup要好得多.如前所述,这是Angular源代码中的常见设计,因此我认为可以肯定地说这是一种可接受的模式.
如果要使子组件更可重用,可以创建构造函数参数@Optional().
以下是我用来测试上述例子的完整资料
- import {
- Component,OnInit,ViewChildren,QueryList,OnDestroy,forwardRef,Injector
- } from '@angular/core';
- import {
- FormControl,FormGroup,ControlContainer,Validators,FormGroupDirective,REACTIVE_FORM_DIRECTIVES
- } from '@angular/forms';
- export abstract class FormControlContainer {
- abstract addControl(name: string,template: `
- <form [formGroup]="form">
- <child-component></child-component>
- <div>
- <button type="button" (click)="onSubmit()">Submit</button>
- </div>
- </form>
- `,forwardRef(() => ChildComponent)],providers: [formGroupContainerProvider]
- })
- export class NestedFormComponentsComponent implements FormControlContainer {
- form = new FormGroup({});
- onSubmit(e) {
- if (!this.form.valid) {
- console.log('form is INVALID!')
- if (this.form.hasError('required',['firstName'])) {
- console.log('First name is required.');
- }
- if (this.form.hasError('required',['lastName'])) {
- console.log('Last name is required.');
- }
- } else {
- console.log('form is VALID!');
- }
- }
- addControl(name: string,control: FormControl): void {
- this.form.addControl(name,control);
- }
- removeControl(name: string): void {
- this.form.removeControl(name);
- }
- }
- @Component({
- selector: 'child-component',template: `
- <div>
- <label for="firstName">First name:</label>
- <input id="firstName" [formControl]="firstName" type="text"/>
- </div>
- <div>
- <label for="lastName">Last name:</label>
- <input id="lastName" [formControl]="lastName" type="text"/>
- </div>
- `,this.lastName);
- }
- ngOnDestroy() {
- this._parent.removeControl('firstName');
- this._parent.removeControl('lastName');
- }
- }