Miguel Galante
Miguel Galante

Reputation: 1699

How to compose multiple custom form controls into 1 Angular2+

I made a custom form control for input a number :

@Component({
    selector: 'number-input',
    templateUrl: './number-input.component.html',
    styleUrls: ['./number-input.scss'],
    changeDetection: ChangeDetectionStrategy.OnPush,
    providers: [
        {
            provide: NG_VALUE_ACCESSOR,
            useExisting: forwardRef(() => NumberInputComponent),
            multi: true
        }
    ]
})
    export class NumberInputComponent implements ControlValueAccessor {
... }

This one works great. And i used it for making a TimePicker with reactive forms:

                <number-input [max]="23" formControlName="startTimeHours"></number-input>
                <number-input [max]="45" [step]="15" formControlName="startTimeMinutes"></number-input>

I would like to encapsulate this timepicker in a single custom form control. Is it posible? How?

Upvotes: 0

Views: 2631

Answers (1)

user7572807
user7572807

Reputation:

It's been a while since I've written Angular code, but this worked pretty well last I used it. It's using an old version of Angular 2, so treat it more like pseudo-code.

You're also going to want to think about error propagation. Have a look at a similar question I had a while ago.

@Component({
  ...
  providers: [{
    provide: NG_VALIDATORS,
    useExisting: CustomDateTimeControl,
    multi: true
  }, {
    provide: NG_VALUE_ACCESSOR,
    useExisting: CustomDateTimeControl,
    multi: true
  }]
})
export class CustomDateTimeControl implements OnInit, ControlValueAccessor, Validator {
  private propagateChange = function (change) { };
  private propagateTouched = function () { };

  // Inner controls (you can also use an internal FormGroup for this)
  public date = new FormControl();
  public time = new FormControl();

  constructor() {}

  ngOnInit() {
    this.date.valueChanges
      .subscribe(value => {
        this.propagateChange(value + ' ' + this.time.value);
        this.propagateTouched();
      }

    this.time.valueChanges
      .subscribe(value => {
        this.propagateChange(this.date.value + ' ' + value);
        this.propagateTouched();
      }
    }

  writeValue(value) {
    // Need to update the inner controls, but don't use setValue / patchValue,
    // as that will trigger valueChanges in the above subscriptions,
    // incorrectly calling touched
  }

  registerOnChange(fn) {
    this.propagateChange = fn;
  }

  registerOnTouched(fn) {
    this.propagateTouched = fn;
  }

  validate(control) {
    // Custom logic to validate the parent control. In this case,
    // we may choose to union all childrens' errors.

    let errors = Object.assign(this.localControl.errors || {}, this.remoteControl.errors || {});
    return Object.keys(errors).length ? errors : null;
  }
}

Upvotes: 3

Related Questions