Search code examples
ionic-frameworkionic4ion-select

Ionic 4 Manual Override of ion-select


Hi I need 'select from list' functionality on a form but with the ability to enter a value manually if needed.  I've been trying ion-select but there doesn't seem to be a way to have a manual override.  Is there a way to do this?

Thanks

For example

    <ion-header>
  <ion-toolbar>
    <ion-title>kitlist testy</ion-title>
  </ion-toolbar>
</ion-header>

<ion-content>
  <form [formGroup]="myForm">
  <ion-list>
    <ion-item>
      <ion-label stacked>Lens</ion-label>
      <ion-select placeholder="Select One">
          <ion-select-option value="f">Female</ion-select-option>
          <ion-select-option value="m">Male</ion-select-option>
        </ion-select>
      <ion-input type="text" formControlName='lens'></ion-input>
    </ion-item>
  </ion-list>


</form>
</ion-content>

will give enter image description here

I want the user to be able to add their own value - which I will then store.

Thanks

Following Sergey's very helpful answer I have tried getting this to work and I'm stuck at inputAlert.onDidDismiss which gives me

Expected 0 arguments, but got 1

Here's the code which I have adjusted for my use case:-

import { Component, OnInit } from "@angular/core";
import { FormBuilder, FormGroup } from "@angular/forms";
import { AlertController } from "@ionic/angular";

@Component({
  selector: "app-kitlist",
  templateUrl: "./kitlist.page.html",
  styleUrls: ["./kitlist.page.scss"]
})
export class KitlistPage implements OnInit {
  kitlist = ["lens1", "lens2", "Custom"];
  currentLens: any;
  myForm: FormGroup;

  constructor(
    private fb: FormBuilder,
    private alertController: AlertController
  ) {
    this.myForm = new FormGroup({});
  }

  ngOnInit() {
    this.myForm = this.fb.group({
      lens: ""
    });

    this.myForm.valueChanges.subscribe(console.log);
  }

  submitForm() {
    console.log("submit");
  }

  selectChanged(selectedLens) {
    if (selectedLens === "Custom") {
      this.inputCustomLensValue();
    } else {
      this.currentLens = selectedLens;
    }
  }

  async inputCustomLensValue() {
    const inputAlert = await this.alertController.create({
      header: "Enter your custom lens:",
      inputs: [{ type: "text", placeholder: "type in" }],
      buttons: [{ text: "Cancel" }, { text: "Ok" }]
    });

    inputAlert.onDidDismiss(data => {
      let customLensName: string = data.data.values[0];
      if (customLensName) {
        let indexFound = this.kitlist.findIndex(
          lens => lens === customLensName
        );
        if (indexFound === -1) {
          this.kitlist.push(customLensName);
          this.currentLens = customLensName;
        } else {
          this.currentLens = this.kit[indexFound];
        }
      }
    });
    await inputAlert.present();
  }
}

Solution

  • Since ion-select under the hood uses alert controller, I would leverage it directly and I would have a couple of alerts working together here:

    In your template:

      <ion-item>
        <ion-label>Hair Color</ion-label>
        <ion-button slot="end" (click)="selectColors()">{{ currentOptionLabel }}
          <ion-icon slot="end" name="arrow-dropdown"></ion-icon>
        </ion-button>
      </ion-item>
    

    You can style ion-button according to your needs.

    Now in your ts file we can import Alert Controller and have 2 of them: one for selecting premade options and one that we will create input type of alert in case our user wants to add custom value.

    I am not using button's handler's methods here to make sure Angular can pick up all the data changes:

    import { Component } from '@angular/core';
    import { AlertController } from '@ionic/angular';
    
    @Component({
      selector: 'app-home',
      templateUrl: 'home.page.html',
      styleUrls: ['home.page.css'],
    })
    export class HomePage {
    
      public colorOptions: Array<{type: string, label: string, value: string}>
    
      public currentColorOptionIndex: number;
    
      public currentOptionLabel: string;
    
      constructor(public alertController: AlertController) {
        this.currentColorOptionIndex = 1;
        this.colorOptions = [
          {
            type: "radio",
            label: "Custom",
            value: "Custom"
          },
          {
            type: "radio",
            label: "Brown",
            value: "Brown",
          },
          {
            type: "radio",
            label: "Dark",
            value: "Dark",
          }
        ]
        this.currentOptionLabel = this.colorOptions[this.currentColorOptionIndex].label;
      }
    
      async selectColors() {
        const radioAlert = await this.alertController.create({
          header: 'Prompt!',
          inputs: this.colorOptions as any,
          buttons: [
            {
              text: 'Cancel',
              role: 'cancel',
              cssClass: 'secondary'
            }, {
              text: 'Ok'
            }
          ]
        });
    
        await radioAlert.present();
    
        radioAlert.onDidDismiss().then((data) => {
          let selectedValue = data.data.values;
          if (selectedValue === 'Custom') {
            this.inputCustomColorValue()
          };
          this.currentColorOptionIndex = this.colorOptions.findIndex(option => option.value == selectedValue)
          this.currentOptionLabel = this.colorOptions[this.currentColorOptionIndex].label;
        })
      }
    
      async inputCustomColorValue() {
        const inputAlert = await this.alertController.create({
          header: 'Enter your custom color:',
          inputs: [
            {
              type: 'text',
              placeholder: 'type in'
            }
          ],
          buttons: [
            {
              text: 'Cancel',
              role: 'cancel',
              cssClass: 'secondary'
            }, {
              text: 'Ok',
            }
          ]
        });
    
        await inputAlert.present();
    
        inputAlert.onDidDismiss().then((data) => {
          let customValue = data.data.values[0];
          let indexFound = this.colorOptions.findIndex(option => option.value == customValue)
          if (indexFound !== -1) {
            this.currentColorOptionIndex = indexFound
          } else {
            this.colorOptions.push(
              {
                type: 'radio',
                label: customValue,
                value: customValue,
              }
            )
            this.currentColorOptionIndex = this.colorOptions.length - 1;
          };
          this.currentOptionLabel = this.colorOptions[this.currentColorOptionIndex].label;
        })
      }
    
    }
    

    Updated: added the fact that now in latest Ionic versions (compared to Stackblitz that uses old 4 beta) onDidDismiss hook returns Promise and requires onDidDismiss.then((data) => {}) syntax vs onDidDismiss((data => {})