1.0.3 • Published 6 years ago

@daubihe/controlled-sub-form v1.0.3

Weekly downloads
1
License
UNLICENSED
Repository
github
Last release
6 years ago

An abstract component to split the complex Angular Reactive Forms

Build Status Coverage Status

Complex Angular reactive forms state management based on Profunctor Optics

Install

npm install --save @daubihe/controlled-sub-form

Usage

Implement a SubForm Component

Create a sub-form controlled component by extending an imported abstract class. Define two functions for the profuctor lense:

  • mapControlValueToSubForm that maps an imported control value to the sub-form
  • mapSubFormValueToControl that maps a sub-form value to the control
import { ControlledSubFormTypedComponent } from '@daubihe/controlled-sub-form';

interface Gender {
  code: string;
}

@Component({
  selector: 'app-gender',
  templateUrl: './gender.component.html'
})
export class GenderComponent extends ControlledSubFormTypedComponent<Gender> {
  public genderLabels = [
    { value: 'MALE', display: 'male' },
    { value: 'FEMALE', display: 'female' }
  ];

  constructor(formBuilder: FormBuilder) {
    super(formBuilder);
  }

  getSubFormConfig() {
    return {
      code: ['', Validators.required]
    };
  }

  protected mapControlValueToSubForm(value): Gender {
    return {
      code: value ? value.code : ''
    };
  }

  protected mapSubFormValueToControl(oldCtrlValue: any, newSubFormValue: Gender): any {
    if (!newSubFormValue.code) {
      return _cloneDeep(oldCtrlValue);
    }
    return { code: newSubFormValue.code };
  }
}

The control's *.html will look as following:

<div [formGroup]="subForm">
  <div *ngFor="let genderLabel of genderLabels; let i = index">
    <label>
      <input type="radio" [value]="genderLabel.value" formControlName="code">
      <span>{{genderLabel.display}}</span>
    </label>
  </div>
  <div *ngIf="subForm.get('code').invalid && (subForm.get('code').dirty || subForm.get('code').touched)" style="color: red;">
    <div *ngIf="subForm.get('code').errors['required']">
      Gender is required.
    </div>
  </div>
</div>

Please notice that subForm FormGroup will be created automatically by the ControlledSubFormTypedComponent class according to the getSubFormConfig method defined in the component.

Use a SubForm Component

Now you can use this gender component in your forms by assigning to it a control (the only @Input() param) that will contain the data. Data will be then mapped to a subForm inside the gender component and re-mapped back once changed

<!-- app.component.html -->
<form [formGroup]="form">
  <app-gender [control]="form.get('gender')"></app-gender>
</form>

The value for the control can be passed down in this way:

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html'
})
export class AppComponent {
  form: FormGroup;
  constructor(private formBuilder: FormBuilder) {
    this.form = this.formBuilder.group({
      gender: {code: 'MALE'}
    }});
  }
}

This allows us to split the validation and form creation logic between our controlled sub-form components and still have a reactive value propagation and validation flow (so the parent does not need to know about the data types of children components and the way how to validate them)