0

So, I'm trying to create a custom component to be used across the application, inside a form or as a standalone. here's what I have so far.

custom-select.ts

@Component({
  selector: 'app-custom-select',
  templateUrl: './custom-select.component.html',
  styleUrls: ['./custom-select.component.scss'],
  viewProviders: [{
    provide: ControlContainer,
    useExisting: FormGroupDirective
  }]
})

export class CustomSelectComponent implements OnInit {
    @Input()
    public selection: any[] = [];

    @Input()
    public controlName: string;

    @Output()
    selectionChange: EventEmitter<any> = new EvenEmitter<an>();

    constructor() {}
    ngOnInit() { //sort option list and do other stuff }

    onSelectionChange(event) {
      this.selectionChange.emit(event)
    }
}

custom-select.html

<ng-select>
  [items]="selection"
  bindValue= "id"
  bindLabel="label"
  [formControlName]="controlName"
  (change)="onSelectionChange($event)"
</ng-select>

now to the parent component app-component.ts

export class appComponent implements OnInit {
    testForm: FormGroup = new FormGroup({
      City: new FormControl(null),
      State: new FormControl(null)
    });

    cities: any[] = [];
    states: any[] = [];

    constructor() {}
    ngOnInit() { // populate cities & states}
    .....
    .....
    onSubmit() {
      console.log("Form Values", this.testFrom.value);
    } 

    testCity(event) { console.log("City: ", event); }
    testState(event) { console.log("State: ", event); }
}

app-component.html

<form [ngForm]="testForm">
  <custom-select
   [selection]="cities"
   controlName="City"
   (onSelectionChange)="testCity($event)"
  ></custom-select>
  <custom-select
   [selection]="states"
   controlName="State"
   (onSelectionChange)="testState($event)"
  ></custom-select>
</form>

Results:

City: {id: 101, label: "Chicago"}  //correct value

State: {id: 20, label: "Illinois"}  //correct value

FormValues: {City: 3, State: 1}     //needs to hold above objects instead of list position of selected items

Problem: I'm getting the correct selection inside the "event", but on form submission I'm getting the position of the selected value in options list not the actual values.

How do I achieve passing the event to the form itself depending on the controlName?

Saher
  • 105
  • 1
  • 13

1 Answers1

0

Your code seems to have several items that can be made better

  • Use FormBuilder to generate your formGroup instances

app.component.ts

constructor (private fb: FormBuilder) {}
export class AppComponent {
  constructor(private fb: FormBuilder) {}
  testForm: FormGroup = this.fb.group({
    City: [null],
    State: [null]
  });

  cities: any[] = [];
  states: any[] = [];

  ngOnInit() {
    this.cities = [{ id: 100, label: "NY City" }];
    this.states = [{ id: 10, label: "NY State" }];
    // populate cities & states}
  }
  onSubmit() {
    console.log("Form Values", this.testForm.value);
  }
}

app.component.html

<hello name="{{ name }}"></hello>
<form [formGroup]="testForm" (submit)="onSubmit()">
    <custom-select [selection]="cities" formControlName="City"></custom-select>
    <custom-select [selection]="states" formControlName="State"></custom-select>
  <button>Submit</button>
</form>
  • Create the custom-control implementing ControlValueAccessor custom-select.component.ts
@Component({
  selector: "custom-select",
  templateUrl: "./custom-select.component.html",
  styleUrls: ["./custom-select.component.css"],
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: CustomSelectComponent,
      multi: true
    }
  ]
})
export class CustomSelectComponent implements ControlValueAccessor {
  @Input() selection: any[];
  onChanges: any = () => {};
  onTouch: any = () => {};
  value: any;
  disabled: boolean;
  constructor() {}
  writeValue(value: any): void {
    this.value = value;
  }
  registerOnChange(fn: any): void {
    this.onChanges = fn;
  }
  registerOnTouched(fn: any): void {
    this.onTouch = fn;
  }
  setDisabledState?(isDisabled: boolean): void {
    this.disabled = isDisabled;
  }
}

custom-select.component.html

<ng-select 
  [items]="selection" 
  bindValue="id" 
  bindLabel="label" 
  [(ngModel)]="value" 
  (ngModelChange)="onChanges(value)">
</ng-select>

The idea is to create a custom form control, See this demo

Owen Kelvin
  • 14,054
  • 10
  • 41
  • 74
  • Thank you for the detailed answer & suggestions for better code. Two things however, when I follow your logic, 1) I'm getting an error about `ngModel cannot be used to register form controls with a parent formGroup directive ` 2) Can I get the form to hold name instead of id? – Saher Oct 29 '20 at 20:20
  • Yes you can. You will simply change `bindValue="id" ` to `bindValue="name" `, That way `` tag will return the name rather than id. See this fork https://stackblitz.com/edit/angular-ivy-custom-form-control-sjpdgt – Owen Kelvin Oct 30 '20 at 03:40
  • If you must use `ngModel` then I would recommend having a look at this answer https://stackoverflow.com/a/40588951/13680115 – Owen Kelvin Oct 30 '20 at 03:44