angular custom component for both template and reactive form

1k Views Asked by At

I'm trying to create a custom component (a customized autocomplete field) but I want to work for both reactive forms and template forms So sometimes the value will come through [(ngModel)] and sometimes i want to provide formControlName

Until now I've had 2 different templates, but everywhere i look it seems as if NG_VALUE_ACCESSOR is handling it by itself, there's just something missing in my implementation.

To make my question clearer If you use a PrimeNg component or any framework, the same component can take an [(ngModel)] or a formControlName and it behaves as a normal component in both cases without special treatment, this is what i want to do

Similar question with same issue :

How to wrap a primeng component like autocomplete using reactive forms?

1

There are 1 best solutions below

0
On

This is an old question, but I was struggling with the same problem. In case anybody stumbles across this, here's how I interpret what is being asked, and my solution.

My component needs to be able to receive a [value] and emit some (output), while ALSO being able to simply receive a formControlName. I too needed to solve this problem. Use the following example:

<my-button-toggle formControlName="toggleValue"></my-button-toggle>

OR

<my-button-toggle
  value="A"
  (toggleChange)="handleChange($event)"
></my-button-toggle>

I fought with this for a while, but after looking at this repo it finally clicked for me.

Here's the html structure of my wrapped button-toggle component

<mat-button-toggle-group
  [disabled]="disabled"
  [multiple]="multiple"
  [vertical]="vertical"
  [value]="value"
  (change)="doChange($event)">
...

And in the ts, we allow for formControlName as well as value setting:

export interface ToggleItem {
  disabled?: boolean
  value?: any
  label: string
}

@Component({
  selector: 'my-button-toggle',
  templateUrl: './button-toggle.component.html',
  styleUrls: ['./button-toggle.component.scss'],
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: ButtonToggleComponent,
      multi: true,
    },
  ],
})
export class ButtonToggleComponent implements ControlValueAccessor {
  @Input() value: any = ''
  @Input() disabled = false
  @Input() multiple = false
  @Input() vertical = false
  @Input() toggles: ToggleItem[] = []
  @Output() toggleChange = new EventEmitter<any>()

  onChange = (value: any) => {}
  onTouched = () => {}

  writeValue(value: string): void {
    this.value = value
  }

  registerOnChange(fn: any): void {
    this.onChange = fn
  }

  registerOnTouched(fn: any): void {
    this.onTouched = fn
  }

  setDisabledState?(isDisabled: boolean): void {
    this.disabled = isDisabled
  }

  doChange = (x: MatButtonToggleChange) => {
    this.value = x.value
    this.onChange(x.value)
    this.toggleChange.emit(x.value)
  }
}

So, what's happening is that when the button-toggle's (change) event is fired, it calls the this.onChange internally, which is handled by the ControlValueAccessor, but then I also tell my custom output to emit the value, so that any other type of non-reactive-form use case can still talk to the component.