Ion-select with Searchbar


#1

I’ve seen a couple of posts here with people asking for an ability to search tems in ion-select.
So, I decided to share my solution for that.

Basically, it’s a component styled to look like ion-select. It allows to search items (including AJAX search).
Here is a Plunker demo and below are some screenshots.

iOS

Android

Adding it to your project

  1. Copy files from select folder to your project.
  2. Add SelectSearchableModule module to imports array of your app module.
import { SelectSearchableModule } from '../components/select/select-module';

@NgModule({
    imports: [ SelectSearchableModule ],
})
export class AppModule { }

  1. Add it to a page.

HTML:

<ion-item>
    <select-searchable
        [(ngModel)]="port"
        title="Port"
        itemValueField="id"
        itemTextField="name"
        [items]="ports"
        [canSearch]="true">
    </select-searchable>
</ion-item>

TypeScript:

import { Component } from '@angular/core';
import { SelectSearchable } from '../components/select/select';

class Port {
    public id: number;
    public name: string;
    public country: string;
}

@Component({
    selector: 'page-home',
    templateUrl: 'home.html'
})
export class HomePage {
    ports: Port[];
    port: Port;

    constructor() {
        this.ports = [
            { id: 0, name: 'Tokai', country: 'Japan' },
            { id: 1, name: 'Vladivostok', country: 'Russia' },
            { id: 2, name: 'Navlakhi', country: 'India' }
        ];
    }

    portChange(event: { component: SelectSearchable, value: any }) {
        console.log('value:', event.value);
    }
}

Using it with Angular Forms

SelectSearchable component is fully compatible with Angular 2 Forms and can be used with both ngModel or FormControl.

Parameters (API)

items: any[]
A list of items.

isSearching: boolean
Use it to show or hide loading spinner for AJAX search.

itemValueField: string
The name of a field which is used as a unique identifier of an item.

itemTextField: string
The name of a field which is used in a list to display an item.

canSearch: boolean
Whether to show the Searchbar.

canReset: boolean
Whether to show the “Clear” button which clears the value.

title: string
Title.

searchPlaceholder: string = 'Enter 3 or more characters’
Placeholder for the Searchbar.

onChange: EventEmitter
An event which is fired when an item is selected from the list.

onSearch: EventEmitter
An event which is fired when a user is typing in the Searchbar.

itemTemplate: Function
A method which is used to create a text to display in the list for an item.
This parameter has a priority over itemTextField.

multiple: boolean
Allows to select multiple items from the list.


#2

hi , where is the select folder ??


#3

Ok, so, first open the demo and download the source code.

Then unzip it and go to components\select folder - and here it is :slight_smile:


#4

hi again, I am using Ionic 3… How do I go about importing in the component module! Or should I import it at app.module.ts level as well in ionic 3


#5

@yasirpanjsheri, I used ionic-angular@3.1.1 in the demo. So, it should be fine for you just to add SelectSearchable and SelectSearchablePage classes to declarations and entryComponents sections of your app.module.ts file.

This how you can import:

import { SelectSearchable } from ‘…/components/select/select’;
import { SelectSearchablePage } from ‘…/components/select/select-page’;

Just make sure you use correct paths.


#6

hi, I followed exactly the demo, but i still get error


#7

Hey @yasirpanjsheri!
Looks like SelectSearchable isn’t a part of your application @NgModule.

Can you share your app.module.ts, template file where you declares directive and its TS file?
What exact ionic-angular and @angular/core versions do you use?


#8

app.module.ts:

  ////
  import { SelectSearchable } from '../components/select/select';
  import { SelectSearchablePage } from '../components/select/select-page';

  @NgModule({
     declarations: [
        MyApp,
        SelectSearchable,
        SelectSearchablePage
     ],
     imports: [
        BrowserModule,
        BrowserAnimationsModule,
        HttpModule,
        IonicModule.forRoot(MyApp, {
            mode: 'md',
            preloadModules: true,
            tabsHideOnSubPages:true
       }),
       IonicStorageModule.forRoot(),
       SDKBrowserModule.forRoot()
      ],
     bootstrap: [IonicApp],
     entryComponents: [
        MyApp,
        SelectSearchable,
        SelectSearchablePage
      ],
    providers: [ ////// ]
   })
  export class AppModule {}

package.json:

  ///////
  "dependencies": {
   "@angular/animations": "^4.0.2",
   "@angular/common": "4.0.2",
   "@angular/compiler": "4.0.2",
   "@angular/compiler-cli": "4.0.2",
   "@angular/core": "4.0.2",
   "@angular/forms": "4.0.2",
   "@angular/http": "4.0.2",
   "@angular/platform-browser": "4.0.2",
   "@angular/platform-browser-dynamic": "4.0.2",
   "@angular/platform-server": "4.0.2",
   "@ionic-native/camera": "^3.5.0",
   "@ionic-native/contacts": "^3.5.0",
   "@ionic-native/core": "^3.5.0",
   "@ionic-native/facebook": "^3.5.0",
   "@ionic-native/file": "^3.5.0",
   "@ionic-native/keyboard": "^3.5.0",
   "@ionic-native/native-page-transitions": "^3.5.0",
   "@ionic-native/network": "^3.5.0",
   "@ionic-native/splash-screen": "^3.5.0",
   "@ionic-native/transfer": "^3.5.0",
   "@ionic/cloud-angular": "^0.9.1",
   "@ionic/storage": "2.0.1",
   "@types/node": "^7.0.14",
   "angular2-elastic": "^0.13.0",
   "angular2-elastic-input": "^1.1.1",
   "capitalize-title": "^1.0.0",
   "font-awesome": "^4.7.0",
   "ionic-angular": "3.1.1",
   "ionic2-auto-complete": "^1.4.2-rc2",
   "ionicons": "3.0.0",
   "jquery": "^3.2.1",
   "lodash": "^4.17.4",
   "ng2-tag-input": "^1.2.4",
   "ng2-truncate": "^1.3.5",
   "rxjs": "5.1.1",
   "socket.io-client": "^1.7.3",
   "sw-toolbox": "3.4.0",
   "zone.js": "^0.8.5"
 },
 ////////

I am using the lazy loading… each component has the decorator @IonicPage() and .module.ts

I copied and pasted the select folder at app level:

--app
   --app.component.ts
   --app.html
   --app.module.ts
   --main.ts
--assets
--components
  --select    // folder
--pages

#9

@yasirpanjsheri ok, as you are using lazy loading, you need to change the structure of the project slightly.

  1. So, first, create select.module.ts file is select folder - /components/select/select.module.ts:
    import { NgModule } from '@angular/core';
    import { IonicPageModule } from 'ionic-angular';
    import { SelectSearchable } from './select';
    import { SelectSearchablePage } from './select-page';

    @NgModule({
        declarations: [
            SelectSearchable,
            SelectSearchablePage
        ],
        imports: [
            IonicPageModule.forChild(SelectSearchable),
            IonicPageModule.forChild(SelectSearchablePage)
        ],
        exports: [
            SelectSearchable,
            SelectSearchablePage
        ],
        entryComponents: [
            SelectSearchable,
            SelectSearchablePage
        ],
    })
    export class SelectSearchableModule { }
  1. Then change app.module.ts:
    // import { SelectSearchable } from '../components/select/select'; // **REMOVE**
    // import { SelectSearchablePage } from '../components/select/select-page'; // **REMOVE**
    import { SelectSearchableModule } from '../components/select/select.module'; // **ADD**

    @NgModule({
        declarations: [
            MyApp
            // SelectSearchable, // **REMOVE**
            // SelectSearchablePage // **REMOVE**
        ],
        imports: [
            BrowserModule,
            BrowserAnimationsModule,
            HttpModule,
            IonicModule.forRoot(MyApp, {
                mode: 'md',
                preloadModules: true,
                tabsHideOnSubPages: true
            }),
            IonicStorageModule.forRoot(),
            SDKBrowserModule.forRoot(),
            SelectSearchableModule // **ADD**
        ],
        bootstrap: [IonicApp],
        entryComponents: [
            MyApp
            // SelectSearchable, // **REMOVE**
            // SelectSearchablePage // **REMOVE**
        ],
        providers: [ ////// ]
    })
    export class AppModule { }
  1. Add SelectSearchableModule to imports array of your “lazy” page (I used LazyPage here as a sample):
    import { LazyPage } from './lazy';
    import { SelectSearchableModule } from '../components/select/select.module';

    @NgModule({
        declarations: [
            LazyPage
        ],
        imports: [
            IonicPageModule.forChild(LazyPage),
            SelectSearchableModule
        ]
    })
    export class LazyPageModule { }
  1. Done! This will work for both normal and lazy loaded pages.

#10

hi, sorry for disturbing you so much… one last thing I wanted to ask, is there a way to pre-fill this custom component.
I am using a form for this… And I want to pre-fill the fields when there is data available… now for the normal fields:

   title: [this.userExp.title, Validators.required] // userExp is list with data

However, doing this for a field that has <select-searchable> does not show anything
I tried using [(ngModel)] but no result as well , and even if it works with ngModel its of no use to me , cuz I am using FormBuilder , the validation wouldn’t work with ngModel
sorry again for the trouble


#11

Yes, you can use it with Angular FormBuilder and Validators. Have a look at the end of this demo. There is a section called Using together with Angular 2 Validators.

A port is pre-selected with Brahestad port initially. Note that a whole port object is passed as a value to FormControl.

this.port8Control = formBuilder.control(this.ports[6], Validators.required);

There is Reset button to clear the port. The text below the port indicates whether the form is valid.


#12

hi, what about pre-filling multiple of items ? can we do that too!


#13

Hey,

Yes, it’s also possible :slight_smile: Just pass an array of objects as a value.

this.port8Control = formBuilder.control([this.ports[3], this.ports[6]]);

#14

the problem I am stuck with is that… the html renders before my function finishes getting the id and name of the selected item.
since the api only displays name of the selected item I created a function to retrieve all the objects (e.g. countries with id) , match the name from the API and then push the matched item name and id in a new array (e.g. country)

so, for form control would be something like

   country: [this.country[0], Validator.required]  // which I am stuck still haven't figured out yet the rendering thing/ maybe if I try to watch for value if any and then call the function to set the value in that order

now for pre-filling multiple of items , I have no clue how to accomplish it like this… any suggestion would be helpful

for clearer image , below is the functions:

 getSelectedCountry(country){         /// the passed data (country) is the data from the API 
      let pre_selected_country = []
          this.getCountries().then(res => {      
             for (let item in res){
                if (country === res[item]['name']){
                 pre_selected_country.push({ id:res[item]['id'], name:res[item]['name'] });
                }
             }
          });
         return pre_selected_country;
  }

  async getCountries(){
    let pack = [];
       await this.userPersoService.getCountries().toPromise().then(res => {
          for(let i in res){
             pack.push({ id:res[i]['id'], name:res[i]['name_en'] });
         }
      }
  });
  return pack;
 }

#15

You can try to set a value after your request has been finished and you have countries.

Here is an example which uses Angular 2 Observables, but you can do something similar in your app:

// Country is not set initially. It will bet set later.
this.countryControl = formBuilder.control(null, Validators.required);

// Get all countires from API.
this.apiService.getCountires().subscribe(countries => {
    // Pre-fill Country control value with first two countries.
    this.countryControl.setValue([countries[0], countries[1]]);
});

#16

Hello, I am getting this Error while trying to ionic serve :

mnowshad@MNowshad-Workbench:~/Dev/cordova-projects/blankApp$ ionic serve
[INFO] Starting app-scripts server: --port 8100 --p 8100 --livereload-port 35729 --r 35729 --address 0.0.0.0 - Ctrl+C to
       cancel
[10:43:02]  watch started ... 
[10:43:02]  build dev started ... 
[10:43:02]  clean started ... 
[10:43:02]  clean finished in 41 ms 
[10:43:02]  copy started ... 
[10:43:02]  transpile started ... 
[10:43:04]  template error, 
            "/home/mnowshad/Dev/cordova-projects/blankApp/src/assets/components/select/components/select/select-page.html":
            Error: ENOENT: no such file or directory, open 
            '/home/mnowshad/Dev/cordova-projects/blankApp/src/assets/components/select/components/select/select-page.html'
[10:43:04]  template error, 
            "/home/mnowshad/Dev/cordova-projects/blankApp/src/assets/components/select/components/select/select.html": 
            Error: ENOENT: no such file or directory, open 
            '/home/mnowshad/Dev/cordova-projects/blankApp/src/assets/components/select/components/select/select.html' 
[10:43:04]  transpile finished in 1.88 s 
[10:43:04]  preprocess started ... 
[10:43:04]  deeplinks started ... 
[10:43:04]  deeplinks finished in 16 ms 
[10:43:04]  preprocess finished in 17 ms 
[10:43:04]  webpack started ... 
[10:43:04]  copy finished in 2.69 s 
[10:43:10]  webpack finished in 5.94 s 
[10:43:10]  sass started ... 
[10:43:10]  sass finished in 849 ms 
[10:43:10]  postprocess started ... 
[10:43:10]  postprocess finished in 3 ms 
[10:43:10]  lint started ... 
[10:43:10]  build dev finished in 8.81 s 
[10:43:11]  watch ready in 9.04 s 
[10:43:11]  dev server running: http://localhost:8100/ 

[INFO] Development server running
       Local: http://localhost:8100
       
[10:43:12]  lint finished in 1.36 s

app.module.ts :

import { BrowserModule } from '@angular/platform-browser';
import { ErrorHandler, NgModule } from '@angular/core';

import { IonicApp, IonicErrorHandler, IonicModule } from 'ionic-angular';
import { SplashScreen } from '@ionic-native/splash-screen';
import { StatusBar } from '@ionic-native/status-bar';

import { MyApp } from './app.component';
import { HomePage } from '../pages/home/home';
import { ItemSubmitPage } from '../pages/item-submit/item-submit';
import { GeneratorService } from '../services/generator.service';
import { SelectSearchableModule } from '../assets/components/select/select-module';

var componentArray = [
  MyApp,
  HomePage,
  ItemSubmitPage
]

@NgModule({
  declarations: [componentArray],
  imports: [
    BrowserModule,
    IonicModule.forRoot(MyApp),
    SelectSearchableModule
  ],
  bootstrap: [IonicApp],
  entryComponents: [componentArray],
  providers: [
    StatusBar,
    SplashScreen,
    {provide: ErrorHandler, useClass: IonicErrorHandler},
    GeneratorService
  ]
})
export class AppModule {}

home.ts :


import { Component, OnInit } from '@angular/core';
import { NavController, App } from 'ionic-angular';

import { ItemSubmitPage } from '../item-submit/item-submit';
import { GeneratorService } from '../../services/generator.service';

import { SelectSearchable } from '../../assets/components/select/select';

@Component({
  selector: 'page-home',
  templateUrl: 'home.html'
})
export class HomePage implements OnInit {
  
  itemSubmitPage:any = ItemSubmitPage;
  largeArray:any = [];
  _hideSelect:boolean = true;
  _itemSelected:number = 0;

  _searchableSelected:any;

  constructor(
    public navCtrl: NavController,
    public appCtrl: App,
    public generatorService: GeneratorService
  ) {}

  ngOnInit(): void {
    this.getLargeArray();
  }

  getLargeArray() {
    this.generatorService.getLargeArray()
    .then(_largeArray => {
      this.largeArray = _largeArray;
      this._hideSelect = false;
    })
  }

  portChange(event: { component: SelectSearchable, value: any }) {
    console.log('value:', event.value);
  }

  pushPage() {
    this.appCtrl.getRootNav().push(this.itemSubmitPage);
  }

}

home.html :

<ion-header>
  <ion-navbar>
    <ion-title>
      Ionic
    </ion-title>
  </ion-navbar>
</ion-header>

<ion-content padding>
  <h2> This is Home state. </h2>

  <ion-item [hidden]="_hideSelect">
    <ion-label> Select Stuff </ion-label>
    <ion-select [(ngModel)]="_itemSelected" required>
      <ion-option *ngFor="let item of largeArray" [value]="item.id">
        {{ item.value }}
      </ion-option>
    </ion-select>
  </ion-item>

<!-- select-searchable start -->
  <ion-item>
    <select-searchable
      [(ngModel)]="_searchableSelected"
      title="large Array"
      itemValueField="id"
      itemTextField="value"
      [items]="largeArray"
      [canSearch]="true">
    </select-searchable>
  </ion-item>
<!-- select-searchable end -->

  <button ion-button block 
    [disabled]="_itemSelected === 0" 
    (click)="pushPage()">
    Next
  </button>

</ion-content>

folder structure :


#17

Hi there,

It looks like paths to HTML files are incorrect:

/home/mnowshad/Dev/cordova-projects/blankApp/src/assets/ components/select/components/select /select-page.html

The /components/select/components/select/ part is included twice.
Check templateUrl: ‘select.html’, property inside both select.ts and select-page.ts files.


#18

Hi there eakoriakin. This library is great and help me much but about one hour ago my ion-script was updated to version 2.0.0-201707111847 because i want to add parallax animation in my apps. My system information is like this :

Your system information:

 ordova CLI: 7.0.1
Ionic Framework Version: 3.5.0-201707061803
Ionic CLI Version: 2.2.3
Ionic App Lib Version: 2.2.1
Ionic App Scripts Version: 2.0.0-201707111847
ios-deploy version: Not installed
ios-sim version: Not installed
OS: Windows 10
Node Version: v6.10.3
Xcode version: Not installed

then when i run ionic serve , it got error error with SelectSearchable like this :

[07:52:25]  typescript: L:/Ionic Apps/ITServiceDesk/src/components/select/select.ts, line: 66
[07:52:25]  typescript: L:/Ionic Apps/ITServiceDesk/src/components/select/select.ts, line: 152             Argument of type 'this' is not assignable to parameter of type 'IonicFormInput'. Type 'SelectSearchable' is
            not assignable to type 'IonicFormInput'. Property 'initFocus' is missing in type 'SelectSearchable'.


      L65:          this.hasSearchEvent = this.onSearch.observers.length > 0;
      L66:          this.ionForm.register(this);

            Argument of type 'this' is not assignable to parameter of type 'IonicFormInput'. Type 'SelectSearchable' is
            not assignable to type 'IonicFormInput'.

     L151:  ngOnDestroy() {
     L152:      this.ionForm.deregister(this);

[07:52:25]  ionic-app-script task: "build"
[07:52:25]  Error: Failed to transpile program
Error: Failed to transpile program
    at BuildError.Error (native)
    at new BuildError (L:\Ionic Apps\ITServiceDesk\node_modules\@ionic\app-scripts\dist\util\errors.js:16:28)
    at L:\Ionic Apps\ITServiceDesk\node_modules\@ionic\app-scripts\dist\transpile.js:137:20
    at transpileWorker (L:\Ionic Apps\ITServiceDesk\node_modules\@ionic\app-scripts\dist\transpile.js:103:12)
    at Object.transpile (L:\Ionic Apps\ITServiceDesk\node_modules\@ionic\app-scripts\dist\transpile.js:61:12)
    at buildProject (L:\Ionic Apps\ITServiceDesk\node_modules\@ionic\app-scripts\dist\build.js:97:78)
    at L:\Ionic Apps\ITServiceDesk\node_modules\@ionic\app-scripts\dist\build.js:47:16

Could you help me please ? Thank you very much


#19

Hi there, try changing select.ts file as follows.

  1. Import IonicFormInput.

import { ..., **IonicFormInput** } from 'ionic-angular';

  1. Add it to implements.

export class SelectSearchable implements ControlValueAccessor, OnDestroy, OnChanges, **IonicFormInput** { ... }

  1. Add an empty method initFocus() { } after ngOnInit().

#20

Hi there again. Thank you for the correction and its worked :smile: and when i run Ionic Serve, there was nothing error in syntax, etc.