Ionic 3: Select, search & autocomplete

Ionic 3 – select, search i autocomplete komponenta

Iako sam već objavio blog post na temu Ionic 3 autocomplete funkcionalnosti mislim da će vam se primjer iz ovog blog posta još više svidjeti. Cijela se funkcionalnost temelji na proširivanju mogućnosti već postojeće Ionic Select komponente.

Postavljanje aplikacije

Za početak je potrebno kreirati novi Ionic projekt

$ ionic start Ionic3SelectSearchAutocomplete blank
$ cd Ionic3SelectSearchAutocomplete

Izrada select komponente

Komponentu možete kreirati sljedećom naredbom:

$ ionic generate component select-search

Struktura datoteka sada izgleda ovako:

--src
 - app
 - assets
 - components
  - select-search
   - select-search-module.ts
   - select-search-page.html
   - select-search-page.scss
   - select-search-page.ts
   - select-search.html
   - select-search.scss
   - select-search.ts
 - pages
 - theme
 - ...

Sadržaj datoteka možete vidjeti u nastavku.

select-search-module.ts

Ovdje definiramo modul koji će Ionic aplikaciji reći od čega se sastoji naša Ionic komponenta te kako ju aplikacija prilikom pokretanja treba interpretirati.

import { NgModule } from '@angular/core';
import { IonicPageModule } from 'ionic-angular';
import { SelectSearch } from './select-search';
import { SelectSearchPage } from './select-search-page';

@NgModule({
    declarations: [
        SelectSearch,
        SelectSearchPage
    ],
    imports: [
        IonicPageModule.forChild(SelectSearch),
        IonicPageModule.forChild(SelectSearchPage)
    ],
    exports: [
        SelectSearch,
        SelectSearchPage
    ],
    entryComponents: [
        SelectSearch,
        SelectSearchPage
    ]
})
export class SelectSearchModule { }

select-search-page.html

<ion-header>
    <ion-navbar>
        <ion-title>{{selectComponent.title}}</ion-title>
    </ion-navbar>
    <ion-toolbar *ngIf="selectComponent.canSearch">
        <ion-searchbar
            #searchbarComponent
            [(ngModel)]="selectComponent.filterText"
            (ionInput)="filterItems()"
            [placeholder]="selectComponent.searchPlaceholder || 'Search'">
        </ion-searchbar>
    </ion-toolbar>
</ion-header>
<ion-content>
    <div class="select-search-spinner" *ngIf="selectComponent.isSearching">
        <div class="select-search-spinner-background"></div>
        <ion-spinner></ion-spinner>
    </div>
    <ion-list no-margin *ngIf="filteredItems.length">
        <button ion-item detail-none *ngFor="let item of filteredItems" (click)="select(item)">
            <ion-icon
                [name]="isItemSelected(item) ? 'checkmark-circle' : 'radio-button-off'"
                [color]="isItemSelected(item) ? 'primary' : 'daek'"
                item-left>
            </ion-icon>
            <h2>{{selectComponent.formatItem(item)}}</h2>
        </button>
    </ion-list>
    <div *ngIf="!filteredItems.length" margin>No items found.</div>
</ion-content>
<ion-footer *ngIf="selectComponent.canReset || selectComponent.multiple">
    <ion-toolbar padding>
        <ion-row>
            <ion-col no-padding *ngIf="selectComponent.canReset"
                [attr.col-6]="selectComponent.canReset && selectComponent.multiple ? '' : null"
                [attr.col-12]="selectComponent.canReset && !selectComponent.multiple ? '' : null">
                <button ion-button full no-margin (click)="reset()" [disabled]="!selectedItems.length">
                    Clear
                </button>
            </ion-col>
            <ion-col no-padding *ngIf="selectComponent.multiple"
                [attr.col-6]="selectComponent.canReset && selectComponent.multiple ? '' : null"
                [attr.col-12]="!selectComponent.canReset && selectComponent.multiple ? '' : null">
                <button ion-button full no-margin (click)="ok()">
                    OK
                </button>
            </ion-col>
        </ion-row>
    </ion-toolbar>
</ion-footer>

select-search-page.scss

.select-search-page {
    &.select-search-page-can-reset.select-search-page-multiple {
        .footer .col:first-child {
            padding-right: $content-margin / 2;
        }
        
        .footer .col:last-child {
            padding-left: $content-margin / 2;
        }
    }
}

select-search-page.ts

import { Component, ViewChild } from '@angular/core';
import { NavParams, NavController, Searchbar } from 'ionic-angular';
import { SelectSearch } from './select-search';

@Component({
    selector: 'select-search-page',
    templateUrl: 'select-search-page.html',
    host: {
        'class': 'select-search-page',
        '[class.select-search-page-can-reset]': 'selectComponent.canReset',
        '[class.select-search-page-multiple]': 'selectComponent.multiple'
    }
})
export class SelectSearchPage {
    selectComponent: SelectSearch;
    filteredItems: any[];
    selectedItems: any[] = [];
    navController: NavController;
    @ViewChild('searchbarComponent') searchbarComponent: Searchbar;

    constructor(private navParams: NavParams) {
        this.selectComponent = navParams.get('selectComponent');
        this.navController = navParams.get('navController');
        this.filteredItems = this.selectComponent.items;
        this.filterItems();

        if (this.selectComponent.value) {
            if (this.selectComponent.multiple) {
                this.selectComponent.value.forEach(item => {
                    this.selectedItems.push(item);
                });
            } else {
                this.selectedItems.push(this.selectComponent.value);
            }
        }
    }

    ngAfterViewInit() {
        if (this.searchbarComponent) {
            // Focus after a delay because focus doesn't work without it.
            setTimeout(() => {
                this.searchbarComponent.setFocus();
            }, 1000);
        }
    }

    isItemSelected(item: any) {
        return this.selectedItems.find(selectedItem => {
            if (this.selectComponent.itemValueField) {
                return item[this.selectComponent.itemValueField] === selectedItem[this.selectComponent.itemValueField];
            }

            return item === selectedItem;
        }) !== undefined;
    }

    deleteSelectedItem(item: any) {
        let itemToDeleteIndex;

        this.selectedItems.forEach((selectedItem, itemIndex) => {
            if (this.selectComponent.itemValueField) {
                if (item[this.selectComponent.itemValueField] === selectedItem[this.selectComponent.itemValueField]) {
                    itemToDeleteIndex = itemIndex;
                }
            } else if (item === selectedItem) {
                itemToDeleteIndex = itemIndex;
            }
        });

        this.selectedItems.splice(itemToDeleteIndex, 1);
    }

    addSelectedItem(item: any) {
        this.selectedItems.push(item);
    }

    select(item: any) {
        if (this.selectComponent.multiple) {
            if (this.isItemSelected(item)) {
                this.deleteSelectedItem(item);
            } else {
                this.addSelectedItem(item);
            }
        } else {
            if (!this.isItemSelected(item)) {
                this.selectedItems = [];
                this.addSelectedItem(item);
                this.selectComponent.select(item);
            }

            this.close();
        }
    }

    ok() {
        this.selectComponent.select(this.selectedItems.length ? this.selectedItems : null);
        this.close();
    }

    close() {
        // Focused input interferes with the animation.
        // Blur it first, wait a bit and then close the page.
        if (this.searchbarComponent) {
            this.searchbarComponent._fireBlur();
        }

        setTimeout(() => {
            this.navController.pop();

            if (!this.selectComponent.hasSearchEvent) {
                this.selectComponent.filterText = '';
            }
        });
    }

    reset() {
        this.navController.pop();
        this.selectComponent.reset();
    }

    filterItems() {
        if (this.selectComponent.hasSearchEvent) {
            if (this.selectComponent.isNullOrWhiteSpace(this.selectComponent.filterText)) {
                this.selectComponent.items = [];
            } else {
                // Delegate filtering to the event.
                this.selectComponent.emitSearch();
            }
        } else {
            // Default filtering.
            if (!this.selectComponent.filterText || !this.selectComponent.filterText.trim()) {
                this.filteredItems = this.selectComponent.items;
                return;
            }

            let filterText = this.selectComponent.filterText.trim().toLowerCase();

            this.filteredItems = this.selectComponent.items.filter(item => {
                return item[this.selectComponent.itemTextField].toLowerCase().indexOf(filterText) !== -1;
            });
        }
    }
}

select-search.html

<div class="select-search-label">{{title}}</div>
<div class="select-search-value">{{formatValue()}}</div>
<div class="select-search-icon">
    <div class="select-search-icon-inner"></div>
</div>
<button aria-haspopup="true" ion-button="item-cover" class="item-cover"></button>

select-search.scss

.item-select-search {
    .label {
        margin-right: 0;
    }
}

.select-search {
    display: flex;

    &-label {
        flex: 1;
        text-overflow: ellipsis;
        overflow: hidden;
    }

    &-value {
        max-width: 45%;
        text-overflow: ellipsis;
        overflow: hidden;
    }

    &-icon {
        position: relative;
        width: 20px;
    }

    &-icon-inner {
        position: absolute;
        top: 50%;
        left: 5px;
        border-top: 5px solid;
        border-right: 5px solid transparent;
        border-left: 5px solid transparent;
        pointer-events: none;
    }

    &-ios {
        .select-search-icon {
            height: 18px;
        }

        .select-search-icon-inner {
            margin-top: -2px;
            color: $select-ios-icon-color;
        }

        .select-search-value {
            padding-left: $select-ios-padding-left;
        }
    }

    &-md {
        .select-search-icon {
            height: 19px;
        }

        .select-search-icon-inner {
            margin-top: -3px;
            color: $select-md-icon-color;
        }

        .select-search-value {
            padding-left: $select-md-padding-left;
        }

        .select-search-label {
            color: $select-md-placeholder-color;
        }
    }

    &-spinner {
        &-background {
            top: 0;
            bottom: 0;
            left: 0;
            right: 0;
            position: absolute;
            background-color: #000;
            z-index: 100;
            opacity: 0.05;
        }

        ion-spinner {
            position: absolute;
            top: 50%;
            left: 50%;
            z-index: 10;
            margin-top: -14px;
            margin-left: -14px;
        }
    }
}

select-search.ts

import { Component, Input, Output, EventEmitter, Optional, OnDestroy, forwardRef, HostListener, OnChanges, SimpleChanges } from '@angular/core';
import { NG_VALUE_ACCESSOR, ControlValueAccessor } from '@angular/forms';
import { Item, Form, NavController, Platform } from 'ionic-angular';
import { SelectSearchPage } from './select-search-page';

@Component({
    selector: 'select-search',
    templateUrl: 'select-search.html',
    providers: [{
        provide: NG_VALUE_ACCESSOR,
        useExisting: forwardRef(() => SelectSearch),
        multi: true
    }],
    host: {
        'class': 'select-search',
        '[class.select-search-ios]': 'isIos',
        '[class.select-search-md]': 'isMd',
        '[class.select-search-can-reset]': 'canReset'
    }
})
export class SelectSearch implements ControlValueAccessor, OnDestroy, OnChanges {
    private _items: any[] = [];
    private isIos: boolean;
    private isMd: boolean;
    filterText = '';
    value: any = null;
    hasSearchEvent: boolean;
    get items(): any[] {
        return this._items;
    }
    @Input('items')
    set items(items: any[]) {
        // The original reference of the array should be preserved to keep two-way data binding working between SelectSearchable and SelectSearchablePage.
        this._items.splice(0, this._items.length);

        // Add new items to the array.
        Array.prototype.push.apply(this._items, items);
    }
    @Input() isSearching: boolean;
    @Input() itemValueField: string;
    @Input() itemTextField: string;
    @Input() canSearch = false;
    @Input() canReset = false;
    @Input() title: string;
    @Input() searchPlaceholder: string = '...';
    @Output() onChange: EventEmitter<any> = new EventEmitter();
    @Output() onSearch: EventEmitter<any> = new EventEmitter();
    @Input() itemTemplate: Function;
    @Input() multiple: boolean;

    constructor(private navController: NavController, private ionForm: Form, private platform: Platform, @Optional() private ionItem: Item) { }

    isNullOrWhiteSpace(value: any): boolean {
        if (value === null || value === undefined) {
            return true;
        }

        // Convert value to string in case if it's not.
        return value.toString().replace(/\s/g, '').length < 1;
    }

    ngOnInit() {
        this.isIos = this.platform.is('ios');
        this.isMd = this.platform.is('android');
        this.hasSearchEvent = this.onSearch.observers.length > 0;
        this.ionForm.register(this);

        if (this.ionItem) {
            this.ionItem.setElementClass('item-select-search', true);
        }
    }

    initFocus() { }

    @HostListener('click', ['$event'])
    _click(event: UIEvent) {
        if (event.detail === 0) {
            // Don't continue if the click event came from a form submit.
            return;
        }

        event.preventDefault();
        event.stopPropagation();
        this.open();
    }

    select(selectedItem: any) {
        this.value = selectedItem;
        this.emitChange();
    }

    emitChange() {
        this.propagateChange(this.value);
        this.onChange.emit({
            component: this,
            value: this.value
        });
    }

    emitSearch() {
        this.onSearch.emit({
            component: this,
            text: this.filterText
        });
    }

    open() {
        this.navController.push(SelectSearchPage, {
            selectComponent: this,
            navController: this.navController
        });
    }

    reset() {
        this.setValue(null);
        this.emitChange();
    }

    formatItem(value: any): string {
        if (this.itemTemplate) {
            return this.itemTemplate(value);
        }

        return value ? value[this.itemTextField] : null;
    }

    formatValue(): string {
        if (!this.value) {
            return null;
        }

        if (this.multiple) {
            return this.value.map(item => this.formatItem(item)).join(', ');
        } else {
            return this.formatItem(this.value);
        }
    }

    private propagateChange = (_: any) => { }

    writeValue(value: any) {
        this.setValue(value);
    }

    registerOnChange(fn: any): void {
        this.propagateChange = fn;
    }

    registerOnTouched(fn: any) { }

    setDisabledState(isDisabled: boolean) { }

    ngOnDestroy() {
        this.ionForm.deregister(this);
    }

    setValue(value: any) {
        this.value = value;

        // Get an item from the list for value.
        // We need this in case value contains only id, which is not sufficient for template rendering.
        if (this.value && !this.isNullOrWhiteSpace(this.value[this.itemValueField])) {
            let selectedItem = this.items.find(item => {
                return item[this.itemValueField] === this.value[this.itemValueField];
            });

            if (selectedItem) {
                this.value = selectedItem;
            }
        }
    }

    ngOnChanges(changes: SimpleChanges) {
        if (changes['items'] && this.items.length > 0) {
            this.setValue(this.value);
        }
    }
}

Što se komponente tiče gore navedene datoteke su sve što vam treba. Za nešto više posjetite https://github.com/eakoriakin/ionic-select-searchable

Kreiranje klase

Nakon što smo kreirali komponentu možemo kreirati i klasu koja će se nalaziti u posebnoj datoteci. Takva vrsta odvajanja olakšava kasnije održavanje aplikacija.

country.ts

export class Country {
    public id: number;
    public name: string;
    public population: number;
}

types.ts

export * from './country';

Struktura datoteka sada izgleda ovako

--src
 - app
 - assets
 - components
  - select-search
   - select-search-module.ts
   - select-search-page.html
   - select-search-page.scss
   - select-search-page.ts
   - select-search.html
   - select-search.scss
   - select-search.ts
 - pages
 - theme
 - types
  - country.ts
  - types.ts
 - ...

Korištenje funkcionalnosti

Imamo komponentu, imamo klasu i sada napokon možemo nešto od svega toga konkretno i napraviti. Ako vam sve napravljeno do sada nije bilo poznato možete odahnuti jer se sada fokusiramo na home.ts i home.html datoteke jer ondje ćemo koristiti ranije kreiranu komponentu.

home.ts

import { Component } from '@angular/core';

import { FormBuilder, FormGroup, FormControl, Validators } from '@angular/forms';
import { SelectSearch } from '../../components/select-search/select-search';
import { Country } from '../../types/types';

@Component({
  selector: 'page-home',
  templateUrl: 'home.html'
})
export class HomePage {

  countries: Country[];
    country1: Country;
    country2: Country;
    country3: Country;
    country4: Country;
    country5: Country;
    country6: Country;
    country7: Country;
    form: FormGroup;
    country8Control: FormControl;

  constructor(private _formBuilder: FormBuilder) {

    this.countries = this.getCountries();
        this.country2 = this.countries[4];
        this.country7 = this.countries[4];
        this.country8Control = _formBuilder.control(this.countries[4], Validators.required);
        this.form = _formBuilder.group({
            country8: this.country8Control
        });

  }

  getCountries(): Country[] {
        return [
            { id: 0, name: 'Afghanistan', population: 27657145 },
            { id: 1, name: 'Albania', population: 2886026 },
            { id: 2, name: 'Austria', population: 8725931 },
            { id: 3, name: 'Bosnia and Herzegovina', population: 3531159 },
            { id: 4, name: 'Croatia', population: 4190669 },
            { id: 5, name: 'France', population: 66710000 },
            { id: 6, name: 'Hungary', population: 9823000 },
            { id: 7, name: 'Indonesia', population: 258705000 },
            { id: 8, name: 'Liechtenstein', population: 37623 },
            { id: 9, name: 'Mexico', population: 122273473 },
            { id: 10, name: 'San Marino', population: 33005 }
        ];
    }

    searchCountries(event: { component: SelectSearch, text: string }) {
        let text = (event.text || '').trim().toLowerCase();

        if (!text) {
            event.component.items = [];
            return;
        } else if (event.text.length < 3) {
            return;
        }

        event.component.isSearching = true;

        // Simulate AJAX.
        setTimeout(() => {
            event.component.items = this.getCountries().filter(country => {
                return country.name.toLowerCase().indexOf(text) !== -1
            });

            event.component.isSearching = false;
        }, 2000);
    }

    countryTemplate(country: Country) {
        return `${country.name}, ${country.population}`;
    }

    countryChange(event: { component: SelectSearch, value: any }) {
        console.log('value:', event.value);
    }

    reset() {
        this.country8Control.reset();
    }

}

home.html

<ion-header>
    <ion-navbar>
        <ion-title>Ionic: Select, search & autocomplete</ion-title>
    </ion-navbar>
</ion-header>
<ion-content>
     <ion-list>
        <ion-list-header>
            Simple Select
        </ion-list-header>
        <ion-item>
            <select-search
                [(ngModel)]="country1"
                title="Country"
                itemValueField="id"
                itemTextField="name"
                [items]="countries"
                (onChange)="countryChange($event)">
            </select-search>
        </ion-item>
    </ion-list> 
     <ion-list>
        <ion-list-header>
            With preselected value
        </ion-list-header>
        <ion-item>
            <select-search
                [(ngModel)]="country2"
                title="Country"
                itemValueField="id"
                itemTextField="name"
                [items]="countries"
                (onChange)="countryChange($event)">
            </select-search>
        </ion-item>
    </ion-list> 
     <ion-list>
        <ion-list-header>
            With Search
        </ion-list-header>
        <ion-item>
            <select-search
                [(ngModel)]="country3"
                title="Country"
                itemValueField="id"
                itemTextField="name"
                [items]="countries"
                [canSearch]="true"
                (onChange)="countryChange($event)">
            </select-search>
        </ion-item>
    </ion-list> 
     <ion-list>
        <ion-list-header>
            With Async Search (AJAX)
        </ion-list-header>
        <ion-item>
            <select-search
                [(ngModel)]="country4"
                title="Country"
                itemValueField="id"
                itemTextField="name"
                [items]="countries"
                [canSearch]="true"
                searchPlaceholder="..."
                (onSearch)="searchCountries($event)"
                (onChange)="countryChange($event)">
            </select-search>
        </ion-item>
    </ion-list> 
     <ion-list>
        <ion-list-header>
            With a custom template for items
        </ion-list-header>
        <ion-item>
            <select-search
                [(ngModel)]="country5"
                title="Country"
                itemValueField="id"
                itemTextField="name"
                [itemTemplate]="countryTemplate"
                [items]="countries"
                [canSearch]="true"
                (onChange)="countryChange($event)">
            </select-search>
        </ion-item>
    </ion-list> 
    <ion-list>
        <ion-list-header>
            Multiple selection
        </ion-list-header>
        <ion-item>
            <select-search
                [(ngModel)]="country6"
                title="Country"
                itemValueField="id"
                itemTextField="name"
                [items]="countries"
                [canSearch]="true"
                [multiple]="true"
                (onChange)="countryChange($event)">
            </select-search>
        </ion-item>
    </ion-list>
    <ion-list>
        <ion-list-header>
            With "Clear" button
        </ion-list-header>
        <ion-item>
            <select-search
                [(ngModel)]="country7"
                title="Country"
                itemValueField="id"
                itemTextField="name"
                [items]="countries"
                (onChange)="countryChange($event)"
                [canReset]="true">
            </select-search>
        </ion-item>
    </ion-list>
    <form [formGroup]="form">
        <ion-list>
            <ion-list-header>
                Using together with Angular 2 Validators
            </ion-list-header>
            <ion-item>
                <select-search
                    formControlName="country8"
                    title="Country"
                    itemValueField="id"
                    itemTextField="name"
                    [items]="countries"
                    [canSearch]="true"
                    (onChange)="countryChange($event)">
                </select-search>
                <button ion-button item-right (click)="reset()" [disabled]="!form.valid">Reset</button>
            </ion-item>
            <ion-item>
                <p>Is valid: {{form.valid}}</p>
            </ion-item>
        </ion-list>
    </form> 
</ion-content>

Na kraju to izgleda ovako

Ionic 3 - select, search i autocomplete komponenta

Prikaz podataka direktno s API-ja

Ovdje ću koristiti https://restcountries.eu/rest/v2/all API. Ovaj sam API koristio u jednom od prethodnih blog postova.

Prvo je potrebno kreirati ApiProvider naredbom:

$ ionic generate provider ApiProvider

Sadržaj izgleda ovako:

import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
import 'rxjs/add/operator/map';

@Injectable()
export class ApiProvider {

  constructor(public _http: Http) {
    console.log('Hello ApiProvider Provider');
  }

   loadData(){
    return this._http.get('https://restcountries.eu/rest/v2/all')
      .map(res => res.json());
  }

}

I sada se opet vraćamo datotekama home.html i home.ts koje ću za potrebe ovog primjera pojednostaviti.

<ion-header>
    <ion-navbar>
        <ion-title>Ionic: Select, search & autocomplete</ion-title>
    </ion-navbar>
</ion-header>
<ion-content>
    <ion-list>
        <ion-list-header>
            Multiple selection
        </ion-list-header>
        <ion-item>
            <select-search
                [(ngModel)]="country"
                title="Country"
                itemValueField="population"
                itemTextField="name"
                [items]="countries"
                [canSearch]="true"
                [multiple]="true"
                (onChange)="countryChange($event)">
            </select-search>
        </ion-item>
    </ion-list>
</ion-content>

Funkcionalnost se sastoji od tri glavna dijela. Prvi dohvaća sve podatke s API-ja, drugi puni novi niz samo sa podacima koje želimo koristiti u aplikaciji i treći se tiče odabira vrijednosti u komponenti na klijentskoj strani.

import { Component } from '@angular/core';

import { SelectSearch } from '../../components/select-search/select-search';
import { Country } from '../../types/types';

import { ApiProvider } from "../../providers/api/api";

@Component({
  selector: 'page-home',
  templateUrl: 'home.html',
  providers: [ApiProvider]
})
export class HomePage {

    countries: Country[];
    country: Country;

    countryOptions:any;

  constructor(public _apiProvider: ApiProvider) {

  }

   ionViewDidLoad() {
    this.getCountries();
  }

     getCountries(){
     this._apiProvider.loadData().subscribe(res => {
     this.countryOptions = res;
     console.log(this.countryOptions);

          let myCountries = [];
            if(this.countryOptions.length > 0) {
                for(let i = 0; i < this.countryOptions.length; i++) {
                    myCountries.push({
                        id: this.countryOptions[i].alpha2Code,
                        population: this.countryOptions[i].population,
                        name: this.countryOptions[i].name
                    });
                }
            } 

         this.countries = this.passingFunction(myCountries);
    
    });
  } 

   passingFunction(data:any): Country[]{
    return data
   }

    countryChange(event: { component: SelectSearch, value: any }) {
        console.log('value:', event.value);

        let myCountries = [];
            if(event.value.length > 0) {
                for(let i = 0; i < event.value.length; i++) {
                    myCountries.push({
                        id: event.value[i].id
                    });
                }
            } 

         console.log(myCountries);

    }

}

Funkcija this.getCountries(); dohvaća popis svih država s API-ja. Unutar te funkcije vrtimo petlju kojom odabiremo koje parametre trebamo od API-ja jer ne trebaju nam svi parametri. Znači uzimao samo id, population i name.

 if(this.countryOptions.length > 0) {
                for(let i = 0; i < this.countryOptions.length; i++) {
                    myCountries.push({
                        id: this.countryOptions[i].alpha2Code,
                        population: this.countryOptions[i].population,
                        name: this.countryOptions[i].name
                    });
                }
            } 

Tako napunjen niz (array) proslijedimo ranije kreiranoj komponenti this.countries = this.passingFunction(myCountries);.

I na kraju kada odaberemo željene vrijednosti opet vrtimo petlju izdvajajući samo id.

let myCountries = [];
            if(event.value.length > 0) {
                for(let i = 0; i < event.value.length; i++) {
                    myCountries.push({
                        id: event.value[i].id
                    });
                }
            } 

Na kraju to izgleda ovako:

Ionic 3 – select, search i autocomplete komponenta

Zaključak

I to je sve. Ova se tema može obraditi na više načina, ali vjerujem da je i ovo dovoljno da dobijete uvid u jedan od načina kako riješiti pitanje Ionic autocomplete funkcionalnosti.

Objavio

Tomislav Stanković

Bloger širokog raspona interesa od kojih dio voli objaviti na ovom blogu. U neslobodno vrijeme Angular developer mobilnih i web aplikacija.

9 misli o “Ionic 3 – select, search i autocomplete komponenta”

  1. Hi. I loved your tutorial and have implemented this in my app. However, I need the functionality to add an option to the list(array) if nothing is found in the search. Could you put up a tutorial on how to do that.

    I am fairly new to Ionic and angular.

  2. Hola , espero que te encuentres bien!!!!

    Mira cuando realizo todos los pasos que indicas, aparece el siguiente error, ¿ me puedes ayudar ?

    Template parse errors: Can’t bind to ‘items’ since it isn’t a known property of ‘select-search’.

    1. If ‘select-search’ is an Angular component and it has ‘items’ input, then verify that it is part of this module.
    2. If ‘select-search’ is a Web Component then add ‘CUSTOM_ELEMENTS_SCHEMA’ to the ‘@NgModule.schemas’ of this component to suppress this message.
    3. To allow any property add ‘NO_ERRORS_SCHEMA’ to the ‘@NgModule.schemas’ of this component

  3. i have this error

    ERROR Error: Uncaught (in promise): Error: Template parse errors:
    Can’t bind to ‘items’ since it isn’t a known property of ‘select-searchable’.

    1. If ‘select-searchable’ is an Angular component and it has ‘items’ input, then verify that it is part of this module.
    2. If ‘select-searchable’ is a Web Component then add ‘CUSTOM_ELEMENTS_SCHEMA’ to the ‘@NgModule.schemas’ of this component to suppress this message.
    3. To allow any property add ‘NO_ERRORS_SCHEMA’ to the ‘@NgModule.schemas’ of this component. (”
    title=”Port”
    [(ngModel)]=”port”
    [ERROR ->][items]=”ports”
    itemValueField=”id”
    itemTextField=”name”
    “): ng:///TransaccionesPageModule/TransaccionesPage.html@23:22

    im use lazy load modules

Odgovori

Vaša adresa e-pošte neće biti objavljena. Obavezna polja su označena sa *