3

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?

Dany Y
  • 6,833
  • 6
  • 46
  • 83
  • must implements ControlValueAccessor, there are full plenty SO, e.g. https://stackoverflow.com/questions/40009149/creating-custom-form-controls-in-angular-2?rq=1 – Eliseo Jul 28 '20 at 09:14
  • Yes of course I already implemented ControlValueAccessor, It's just having one template for both template and reactive form that's the issue. – Dany Y Jul 28 '20 at 10:14
  • @DanyY you can emit Output value from your custom component and set that value to form control in other component. – Kamran Khatti Jul 28 '20 at 10:22
  • I don't know if my question is clear, but for example if you use a primeng component, 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. – Dany Y Jul 28 '20 at 10:23
  • Sounds like you may enjoy https://github.com/cloudnc/ngx-sub-form – maxime1992 Jul 28 '20 at 10:45
  • 1
    Danny, all component that implements ControlValueAccesor create a coponent that you can use in a ReactiveForm and in template Driven From. you has severals examples, another e.g. https://stackblitz.com/edit/angular-vkika9?file=src%2Fapp%2Fcheck-box-group.component.ts. See that you need has as provider of the component some like `providers: [{provide:NG_VALUE_ACCESSOR,useExisting: forwardRef(() =>YourCustomComponent),multi: true}]` Really I don't know how help you. If you create a stackblitz or edite your question adding the code you use... – Eliseo Jul 28 '20 at 16:41
  • Ok thanks @Eliseo, than I think my question is something else, it's because i'm making a wrapper around primeng autocomplete, so in the html the p-autocomplete takes formcontrolname or ngmodel, and don't know which one to use for both, I think I should make another question about it right? this is not the problem i thought it was ? – Dany Y Jul 31 '20 at 07:24

1 Answers1

0

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.

bentedder
  • 796
  • 6
  • 21