2016-11-25 58 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將不會被創建)。

這可能不是最好的解決方案,但它很適合我的項目,因爲我迫使用戶遵循我的步驟。 給我你的反饋。 :)