2016-11-25 63 views
9

在我工作的,我们正在开发多种形式的大规模应用,用户需要填写,以便为我们的程序注册公司。当所有问题都得到解答后,用户就会到达一个总结所有答案的部分,突出显示无效答案,并让用户有机会重新访问任何前面的表单步骤并修改答案。该逻辑将在一系列顶级部分中重复出现,每个部分都有多个步骤/页面和一个摘要页面。角2 - 大规模的申请表格处理

为了实现这一点,我们已经创建了每个单独步骤的形式的组分(它们是类别,如“个人信息”或“资格”等)与它们各自的路线沿着和用于摘要页面的部件。

为了保持尽可能干燥,我们开始创建一个“主”服务,适用于所有不同形式的步骤(值,有效性等)的信息。

import { Injectable } from '@angular/core'; 
import { Validators } from '@angular/forms'; 
import { ValidationService } from '../components/validation/index'; 

@Injectable() 
export class FormControlsService { 
    static getFormControls() { 
    return [ 
     { 
     name: 'personalDetailsForm$', 
     groups: { 
      name$: [ 
      { 
       name: 'firstname$', 
       validations: [ 
       Validators.required, 
       Validators.minLength(2) 
       ] 
      }, 
      { 
       name: 'lastname$', 
       validations: [ 
       Validators.required, 
       Validators.minLength(2) 
       ] 
      } 
      ], 
      gender$: [ 
      { 
       name: 'gender$', 
       validations: [ 
       Validators.required 
       ] 
      } 
      ], 
      address$: [ 
      { 
       name: 'streetaddress$', 
       validations: [ 
       Validators.required 
       ] 
      }, 
      { 
       name: 'city$', 
       validations: [ 
       Validators.required 
       ] 
      }, 
      { 
       name: 'state$', 
       validations: [ 
       Validators.required 
       ] 
      }, 
      { 
       name: 'zip$', 
       validations: [ 
       Validators.required 
       ] 
      }, 
      { 
       name: 'country$', 
       validations: [ 
       Validators.required 
       ] 
      } 
      ], 
      phone$: [ 
      { 
       name: 'phone$', 
       validations: [ 
       Validators.required 
       ] 
      }, 
      { 
       name: 'countrycode$', 
       validations: [ 
       Validators.required 
       ] 
      } 
      ], 
     } 
     }, 
     { 
     name: 'parentForm$', 
     groups: { 
      all: [ 
      { 
       name: 'parentName$', 
       validations: [ 
       Validators.required 
       ] 
      }, 
      { 
       name: 'parentEmail$', 
       validations: [ 
       ValidationService.emailValidator 
       ] 
      }, 
      { 
       name: 'parentOccupation$' 
      }, 
      { 
       name: 'parentTelephone$' 
      } 
      ] 
     } 
     }, 
     { 
     name: 'responsibilitiesForm$', 
     groups: { 
      all: [ 
      { 
       name: 'hasDrivingLicense$', 
       validations: [ 
       Validators.required, 
       ] 
      }, 
      { 
       name: 'drivingMonth$', 
       validations: [ 
       ValidationService.monthValidator 
       ] 
      }, 
      { 
       name: 'drivingYear$', 
       validations: [ 
       ValidationService.yearValidator 
       ] 
      }, 
      { 
       name: 'driveTimesPerWeek$', 
       validations: [ 
       Validators.required 
       ] 
      }, 
      ] 
     } 
     } 
    ]; 
    } 
} 

即正在使用的所有组件以设置用于每个HTML形式绑定,通过,以及通过摘要页面访问相应对象密钥和创建嵌套形式组的服务,其表示层仅受单向约束(模型 - >视图)。

export class FormManagerService { 
    mainForm: FormGroup; 

    constructor(private fb: FormBuilder) { 
    } 

    setupFormControls() { 
     let allForms = {}; 
     this.forms = FormControlsService.getFormControls(); 

     for (let form of this.forms) { 

      let resultingForm = {}; 

      Object.keys(form['groups']).forEach(group => { 

       let formGroup = {}; 
       for (let field of form['groups'][group]) { 
        formGroup[field.name] = ['', this.getFieldValidators(field)]; 
       } 

       resultingForm[group] = this.fb.group(formGroup); 
      }); 

      allForms[form.name] = this.fb.group(resultingForm); 
     } 

     this.mainForm = this.fb.group(allForms); 
    } 

    getFieldValidators(field): Validators[] { 
     let result = []; 

     for (let validation of field.validations) { 
      result.push(validation); 
     } 

     return (result.length > 0) ? [Validators.compose(result)] : []; 
    } 
} 

后,我们开始使用的组件以下语法,以达到在主表单服务指定的表单控件:

personalDetailsForm$: AbstractControl; 
streetaddress$: AbstractControl; 

constructor(private fm: FormManagerService) { 
    this.personalDetailsForm$ = this.fm.mainForm.controls['personalDetailsForm$']; 
    this.streetaddress$ = this.personalDetailsForm$['controls']['address$']['controls']['streetaddress$']; 
} 

这似乎是在我们缺乏经验的眼睛代码味道。考虑到我们最终将拥有的部分数量,我们对这样的应用程序如何扩展会有强烈的担忧。

我们已经讨论了不同的解决方案,但我们不能拿出一个利用角的形式引擎,使我们能够保持我们的验证层次结构完好无损,也很简单。

有没有更好的方法来实现我们正在尝试做的事情?

回答

1

我对@ngrx/store的其他地方发表了评论,虽然我仍然推荐它,但我相信我误解了你的问题。

无论如何,你的FormsControlService基本上是一个全局常量。认真地,用

替换 export class FormControlService ...
export const formControlsDefinitions = { 
    // ... 
}; 

它和它有什么不同?您只需导入对象,而不是获得服务。而且,由于我们现在想的是作为一个类型的常量全球性的,我们可以定义我们使用的界面...

export interface ModelControl<T> { 
    name: string; 
    validators: ValidatorFn[]; 
} 

export interface ModelGroup<T> { 
    name: string; 
    // Any subgroups of the group 
    groups?: ModelGroup<any>[]; 
    // Any form controls of the group 
    controls?: ModelControl<any>[]; 
} 

因为我们做到了这一点,我们就可以单独移动表单组的定义脱离单个单片模块并定义我们定义模型的窗体组。更干净。

// personal_details.ts 

export interface PersonalDetails { 
    ... 
} 

export const personalDetailsFormGroup: ModelGroup<PersonalDetails> = { 
    name: 'personalDetails$'; 
    groups: [...] 
} 

但现在我们已经遍布我们的模块也没有办法将它们收集齐全所有这些个体形态组定义:(我们需要一些方法来知道所有形式组在我们的应用程序。

但是我们不知道将来会有多少个模块,我们可能需要延迟加载它们,所以他们的模型组可能不会在应用程序启动时注册。

将控制反转为救援!一个服务,只有一个注入的依赖项 - 一个可以注入的多提供者当我们将它们分发到我们的模块中时,我们所有的分散的表单组。

export const MODEL_GROUP = new OpaqueToken('my_model_group'); 

/** 
* All the form controls for the application 
*/ 
export class FormControlService { 
    constructor(
     @Inject(MMODEL_GROUP) rootControls: ModelGroup<any>[] 
    ) {} 

    getControl(name: string): AbstractControl { /etc. } 
} 

然后创建一个清单模块的地方(将其注入到“核心”应用模块),构建FormService

@NgModule({ 
    providers : [ 
    {provide: MODEL_GROUP, useValue: personalDetailsFormGroup, multi: true} 
    // and all your other form groups 
    // finally inject our service, which knows about all the form controls 
    // our app will ever use. 
    FormControlService 
    ] 
}) 
export class CoreFormControlsModule {} 

现在,我们已经有了一个解决方案是:

  • 更多本地,表单控件声明与模型
  • 更具可伸缩性,只需要添加一个表单控件,然后将其添加到清单模块;和
  • 不那么单一,没有“神”配置类。
+0

谢谢@ovangle!我认为你的建议非常接近我想要达到的目标,并提供足够的思考。 – Manolis

-1

是否真的有必要在服务中保留表单控件?为什么不把服务作为数据的守护者,并在组件中拥有表单控件?您可以使用CanDeactivate警卫来防止用户从具有无效数据的组件导航。

https://angular.io/docs/ts/latest/api/router/index/CanDeactivate-interface.html

+0

事情是,我们不希望阻止用户从组件导航,即使存在无效数据。摘要页面的目的是防止他们提交应用程序,除非所有数据都有效,但在此之前不会。 一开始,我们在组件中使用了表单控件,并为所有http请求提供服务。但随着应用程序越来越大,这很快就变成了非DRY。 我想我的问题是,考虑到我们将拥有大量的表单控件,我们如何才能实现每个控件的应用程序范围的表单状态意识。 – Manolis

+1

我认为[@ ngrx/store](https://github.com/ngrx/store)结合[@ ngrx/effects](https://github.com/ngrx/effects)就是您要找的内容为了解决你的问题。 它旨在解决您的确切使用案例,跟踪应用程序中的状态,作为您在组件之间传递的离散捆绑包。 – ovangle

+0

应用程序商店将允许您跟踪用户在您的任何表单中提供的所有信息,而不管其有效性如何,商店的“效果”模块可让您设置任何数据子集的有效性在提交完整的表单状态之前。 – ovangle

1

我做了一个类似的应用程序。问题在于,您正在同时创建所有输入,这不可能扩展。

在我的情况,我没有谁管理FormGroup数组的FormManagerService。每个步骤都有一个FormGroup,它通过将FormGroup配置发送到FormManagerService,在步骤组件的ngOnInit上执行时初始化一次。类似的东西:

stepsForm: Array<FormGroup> = []; 
getFormGroup(id:number, config: Object): FormGroup { 
    let formGroup: FormGroup; 
    if(this.stepsForm[id]){ 
     formGroup = this.stepsForm[id]; 
    } else { 
     formGroup = this.createForm(config); // call function to create FormGroup 
     this.stepsForm[id] = formGroup; 
    } 
    return formGroup; 
} 

你需要一个id来知道哪个FormGroup对应的步骤。但在此之后,您将能够在每一步中拆分您的Forms配置(所以小型配置文件比维护更容易维护)。它将最小化初始加载时间,因为FormGroups只在需要时创建。

最后提交之前,你只需要映射您FormGroup阵列和验证,如果他们都有效。只要确保所有步骤已被访问(否则某些FormGroup将不会被创建)。

这可能不是最好的解决方案,但它很适合我的项目,因为我迫使用户遵循我的步骤。 给我你的反馈。 :)