admin管理员组

文章数量:1289845

I'm trying to find a better approach to handle plex angular forms. The form is really big and I need to find an approach to reduce plexity.

Here is an example of form structure:

{
    "fieldA" : ...,
    "fieldB" : ...,
    "fieldC" : ...,
    "profile": {
        "username": ...,
        "email": ...,
        "firstName": ...,
        "lastName": ...,
        ...
    },
    "settings": {
        "enableEmailNotification": ...,
        "notificationsEmail": ..., // required when enableEmailNotification
        ...
    },
    ...
}

There are cases when validators are changed on the fly, for example when enableEmailNotification=true, ponent will add Required validator to notificationsEmail

Here are researched options:

Option #0 - Classic

sample on github

This approach uses one form and one ponent.

Pros:

  • A lot of code, but very simple

Cons:

  • All logic is in one place. For my case this ponent bees too big and hard readable or maintainable
  • UI also bees big enough

Option #1 - Passing FormGroup to subponent

sample on github

This approach sends formGroup to inner ponents as @Input() property.

Pros:

  • Reduces part of the view

Cons:

  • Form creation and validation rules are still on the parent ponent
  • Only view size is reduced
  • Validation logic is created in root ponent, but display errors in sub-ponent

Option #2 - Creating custom ControlValueAccessor

sample on github

Based on a this article we can create custom ControlValueAccessor which will return an object for a part of a form.

Pros:

  • Splits form in multiple forms. Form can be splitten in smaller independent parts.

Cons:

  • Keeping JS object for a form value. Doesn't look very good

I'm trying to find a better approach to handle plex angular forms. The form is really big and I need to find an approach to reduce plexity.

Here is an example of form structure:

{
    "fieldA" : ...,
    "fieldB" : ...,
    "fieldC" : ...,
    "profile": {
        "username": ...,
        "email": ...,
        "firstName": ...,
        "lastName": ...,
        ...
    },
    "settings": {
        "enableEmailNotification": ...,
        "notificationsEmail": ..., // required when enableEmailNotification
        ...
    },
    ...
}

There are cases when validators are changed on the fly, for example when enableEmailNotification=true, ponent will add Required validator to notificationsEmail

Here are researched options:

Option #0 - Classic

sample on github

This approach uses one form and one ponent.

Pros:

  • A lot of code, but very simple

Cons:

  • All logic is in one place. For my case this ponent bees too big and hard readable or maintainable
  • UI also bees big enough

Option #1 - Passing FormGroup to subponent

sample on github

This approach sends formGroup to inner ponents as @Input() property.

Pros:

  • Reduces part of the view

Cons:

  • Form creation and validation rules are still on the parent ponent
  • Only view size is reduced
  • Validation logic is created in root ponent, but display errors in sub-ponent

Option #2 - Creating custom ControlValueAccessor

sample on github

Based on a this article we can create custom ControlValueAccessor which will return an object for a part of a form.

Pros:

  • Splits form in multiple forms. Form can be splitten in smaller independent parts.

Cons:

  • Keeping JS object for a form value. Doesn't look very good
Share Improve this question asked Aug 15, 2019 at 22:08 Maxian NicuMaxian Nicu 2,2943 gold badges19 silver badges32 bronze badges
Add a ment  | 

2 Answers 2

Reset to default 5

My strategy for large plex forms is to have a wrapper ponent and sub ponents. each sub ponent has it's own form service and the wrapper has a master form service with the sub form services injected, consider this

@Component({
  selector: 'form-wrapper',
  template: `
    <form [formGroup]="form" (submit)="save()">
      <sub-form-a></sub-form-a>
      <sub-form-b></sub-form-b>
      <input type="submit" value="Submit Form">
    </form>
  `,
  providers: [MasterFormService, FormAService, FormBService]
})
export class FormWrapper {
  constructor(private formService: MasterFormService) { }
  save() {
    // whatever save actions here
  }
}

@Component({ // form b poent is basically the same
  selector: 'sub-form-a',
  template: `
    ... whatever template belongs to form a ..
  `
})
export class FormAComponent {
  form: FormGroup
  constructor(private formService: FormAService) {
    this.form = this.formService.form;
    // form a specific actions
  }
}

@Injectable()
export class MasterFormService {
  form: FormGroup;
  constructor(private fb: FormBuilder, formAService: FormAService, formBService: FormBService) {
    this.form = this.fb.group({
      groupA: this.formAService.form,
      groupB: this.formBService.form,
    });
  }
}

@Injectable() // formB service is basically the same
export class FormAService {
  form: FormGroup;
  constructor(private fb: FormBuilder) {
    this.form = this.fb.group({
      .. whatever fields belong to form a ..
    });
  }
}

this method creates highly reusable sub forms and lets you modularize / isolate form logic and templates. I often find that sub forms typically belong in more than one place anyway, so it keeps my code very DRY. In your example in particular, you can easily reuse the settings form and profile form ponents elsewhere in your application. One or twice I've even nested this structure again for an extremely plex form.

The con is that the structure may appear plex but you get used to it quick.

Personally, for large plex forms, I'd like to keep the form logic in one ponent, so it's easy to chain the observables, but use a couple of Services as helpers. Both for initializing the form, and handling the actions (returning new form values, Validation, authorization, etc)

  • The logic/tracking is in the FormComponent
  • Initialization of the Form in is in the FormInitService
  • Actions are handled in one pr multiple FormActionService(s)

FormComponent

export class FormComponent implements OnInit {
  constructor(
    private formActionService: FormActionService,
    private formInitService: FormInitService
  ) { }

  ngOnInit() {
    this.form = this.FormInitService.getForm();
    this._trackFieldA();
    this._trackProfile();
  }

  // Track a single field
  private _trackFieldA() {
    this.form.controls.fieldA.valueChanges.pipe(
      distinctUntilChanged((a, b) => JSON.stringify(a) === JSON.stringify(b))
    ).subscribe(fieldA => {
      console.log('Field A Changed');
      this.formActionService.doSomething();
    });
  }

  // Track a group
  // Use ['controls'] for nested controls to skip typechecking errors
  private _trackProfile() {
    bineLatest(
      this.form.controls.profile['controls'].username.valueChanges,
      this.form.controls.profile['controls'].email.valueChanges,
    ).pipe(
      distinctUntilChanged((a, b) => JSON.stringify(a) === JSON.stringify(b))
    ).subscribe(profile => {
      console.log('Some profile field changed');
      this.formActionService.doSomething();
    });
  }
}

FormInitService

export class FormInitService {
  constructor(
    private formBuilder: FormBuilder
  ) { }

  public getForm(): FormGroup {
    return this.formBuilder.group({
      fieldA: 'Some init value',
      fieldB: 'Some init value',
      profile: this.formBuilder.group({
        username: 'Some init value',
        email: 'Some init value',
        ...
      }),
      ...
    });
  }
}

FormActionService

export class FormActionService {
  public doSomething(): any | void {
    console.log('Something')
  }
}

You still have quite some code in the FormComponent and template, but it's really easy to read and maintain. Splitting in multiple Components can often bee very confusing, especially when working in teams, or when some (huge) refactor needs to be done.

本文标签: javascriptHandling big reactive forms in Angular 7Stack Overflow