Search code examples
javascriptangulartypescriptangular-reactive-formsangular4-forms

How to set and get form values when we use css dropdown as shared component


I have a shared component which is a html and css dropdown. I am calling this shared component from a parent component with different data.

For example I have 3 instances of shared component from parent component, so parent form group will have 3 formControls. Since all the 3 formcontrols are now a shared component. How to set and get selected data from all the 3 dropdowns.

Also if any default item is to be set for any dropdown, how can we achieve it.

Main objective here is to access/get all the formControlName values in parent component form group.

I have attached demo code https://stackblitz.com/edit/angular-mncdy5 Please help as I am in a learning stage!


Solution

  • You need to implement ControlValueAccessors in the shared component so you can attach formControls to them in parent component. Here is a great article on that its preety straight forward.

    https://medium.com/@majdasab/implementing-control-value-accessor-in-angular-1b89f2f84ebf

    This is how your customDropdown component needs to look to implement ControlValueAccessors

    import { OnInit } from '@angular/core';
    import { Component, forwardRef, HostBinding, Input } from '@angular/core';
    import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
    
    @Component({
      selector: 'app-common-dropdown',
      templateUrl: './common-dropdown.component.html',
      styleUrls: ['./common-dropdown.component.css'],
      providers: [     
          {       provide: NG_VALUE_ACCESSOR, 
                  useExisting: forwardRef(() => CommonDropdownComponent),
                  multi: true     
          }
        ] 
    })
    export class CommonDropdownComponent implements ControlValueAccessor {
    
      @Input() placeHolder: string;
      @Input() dropDownId: string;
      @Input() dataList: any;
    
      onChange: any = () => {}
      onTouch: any = () => {}
      val= "" // this is the updated value that the class accesses
    
      set value(val){  // this value is updated by programmatic changes if( val !== undefined && this.val !== val){
        this.val = val
        this.onChange(val)
        this.onTouch(val)
      }
    
    
      constructor() { }
    
      ngOnInit() {
      }
    
      // this method sets the value programmatically
      writeValue(value: any){ 
        this.value = value
      }
      registerOnChange(fn: any){
        this.onChange = fn
      }
      registerOnTouched(fn){
        this.onTouch = fn
      }
    
      propagateChange(_){
    
      }
    
      selectClicked(event: any) {
        const ele = event.srcElement.parentNode;
        ele.classList.toggle('cs-active');
      }
    
      selectedOption(ctrl: string, value: string) {
        this.onChange(value) // <-- CRUCIAL need to inform formControl to update the value 
        document.getElementById(ctrl).innerHTML = value;
        const ele = document.getElementById(ctrl).parentElement;
        ele.classList.toggle('cs-active');
      }
    
      closeDropDown(event: any) {
        const ele = event.srcElement;
        ele.classList.remove('cs-active');
      }
    }
    

    Now that we've added the ability to attach formControls to you custom component you can add them to the html in you app.component:

    <form [formGroup]="parentForm">
        <app-common-dropdown placeHolder="select district" [dropDownId]="'districtLabel'" [dataList]="['bangalore','chennai','pune']" formControlName="district" ></app-common-dropdown>
        <app-common-dropdown placeHolder="select distance" [dropDownId]="'distanceLabel'" [dataList]="[100,200,300,400]" formControlName="distance" ></app-common-dropdown>
        <app-common-dropdown placeHolder="select state" [dropDownId]="'stateLabel'" [dataList]="['karnataka','tamil nadu','mumbai']" formControlName="state"  ></app-common-dropdown>
    </form>
    
    <button type="submit" (click)="getFormValues()">submit</button>
    

    And you will need to adjust the names in you formGroup since they didnt match the html form

    this.parentForm = this.fb.group({
      district: ['bangalore', Validators.required], <--- SETS THE DEFAULT VALUE OF THE FORM CONTROL
      distance: [''],
      state:['']
    });
    

    formControlName needs to match a property inside the parentForm.