Adding the Multiselect feature to an Angular2 Autocomplete

Angular2 Multi Selection Autocomplete
Angular2 Multi Selection Autocomplete

After I wrote the tutorial on how to create an angular2 autocomplete, lots of people have been asking me to add the multiselect feature to it, that’s the reason why I decided to write this one. I’m going to continue from where I stopped, so make sure you followed and understood every step from the previous tutorial before you proceed.

The first thing we have to do is to create a variable called selected, it’s just an array we’re going to use to store all the items selected by the user.

public selected = [];

Now let’s change a little bit the select() function, instead of assigning the item to the this.query variable we’re going to add it to the selected array and clean the this.query. This is the modification that will make the multiple selection possible. Here’s the code after the modification:

select(item){
    this.selected.push(item);
    this.query = '';
    this.filteredList = [];
}

We also need a way to remove items from the array, I’m going to create a function called remove (obviously), here’s the code:

remove(item){
    this.selected.splice(this.selected.indexOf(item),1);
}

This is a really straightforward function, it just receives an item and uses the splice() function to remove it from the array.

The template will also suffer some changes:

<div class="container" >
    <div class="input-field col s12">
      <input id="country" type="text" class="validate filter-input" [(ngModel)]=query (keyup)=filter()>
      <label for="country">Country</label>
    </div>
    <div class="suggestions" *ngIf="filteredList.length > 0">
        <ul *ngFor="#item of filteredList" >
            <li >
                <a (click)="select(item)">{{item}}</a>
            </li>
        </ul>
    </div>
    <div *ngFor="#item of selected">
        <div class="selected" >
            <span>{{item}}</span>
            <a (click)="remove(item)">x</a>
        </div>
    </div>
</div>

Notice that I’ve added another div bellow the suggestions list, that’s where the selected items will be displayed, also notice that each item will an ‘x’ that will call the remove function when clicked.

Lastly we have to do the styling, in this example I’ve used the following css to adjust how the selected items are displayed, but feel free to do as you like.

.selected{
	border:solid #4CAF50 1px;
	float:left; 
	margin:2px;
	padding:2px 15px;
}
 
.selected a{
	cursor:pointer;
	font-weight:bold;
}

That’s it guys! With just a few changes we can now select multiple items with our autocomplete, just leave a comment if you have any doubts.

Tutorial: Creating an Angular2 Autocomplete

Angular 2 Autocomplete
Angular 2 Autocomplete

Before we begin I just want to say that I’m not going to use any third-party component for this tutorial, I’m going to create an autocomplete from scratch, my goal is to just make it show the suggestions and allow the user to select one, but if you need more advanced features feel free to comment, I’ll do what I can to help you.

With that in mind, let’s start by creating our class, just copy/paste the code bellow:

export class AutocompleteComponent {
    public query = '';
    public countries = [ "Albania","Andorra","Armenia","Austria","Azerbaijan","Belarus",
                        "Belgium","Bosnia & Herzegovina","Bulgaria","Croatia","Cyprus",
                        "Czech Republic","Denmark","Estonia","Finland","France","Georgia",
                        "Germany","Greece","Hungary","Iceland","Ireland","Italy","Kosovo",
                        "Latvia","Liechtenstein","Lithuania","Luxembourg","Macedonia","Malta",
                        "Moldova","Monaco","Montenegro","Netherlands","Norway","Poland",
                        "Portugal","Romania","Russia","San Marino","Serbia","Slovakia","Slovenia",
                        "Spain","Sweden","Switzerland","Turkey","Ukraine","United Kingdom","Vatican City"];
    public filteredList = [];
    public elementRef;
 
    constructor(myElement: ElementRef) {
        this.elementRef = myElement;
    }
}

As you can see I’ve already added the data to my class, as I’m trying to make it simple it’s just an array of countries, but of course in a real case scenario the data would come from your database or via web service, also it wouldn’t be placed inside the component, it would be passed to it, this way the component could be reused with other data in other parts of the application.

I’ve also created the variables query and filteredList, the first one will hold the string typed by the user, and the second is where I’m going to store the suggestions being displayed by the component, its value will constantly change as the user types on the input. There is also the constructor where I’m setting the elementRef value, but I’ll get to that variable in a moment.

Now let’s create the methods to filter the data and select the item:

filter() {
    if (this.query !== ""){
        this.filteredList = this.countries.filter(function(el){
            return el.toLowerCase().indexOf(this.query.toLowerCase()) > -1;
        }.bind(this));
    }else{
        this.filteredList = [];
    }
}
 
select(item){
    this.query = item;
    this.filteredList = [];
}

These two functions are very straightforward, the filter() function uses the query variable to filter the countries, then it stores the result in the filteredList.

The select() is even simpler, I’m just assigning the selected item to the query variable in order to make it appear on the input, and to make the suggestions list disappear I’m removing everything from the filteredList.

Ok, we already have the two most important functions to make our component work, now we can create the template:

@Component({
    selector: 'autocomplete',
    template: `
        <div class="container" >
            <div class="input-field col s12">
              <input id="country" type="text" class="validate filter-input" [(ngModel)]=query (keyup)=filter()>
              <label for="country">Country</label>
            </div>
            <div class="suggestions" *ngIf="filteredList.length > 0">
                <ul *ngFor="#item of filteredList" >
                    <li >
                        <a (click)="select(item)">{{item}}</a>
                    </li>
                </ul>
            </div>
        </div>  	
        `
})

Note that I’ve used some different css classes here, some of them I created myself and some are from materialize, if you want to use them just add to your project the link to their css and js files.

At this point your autocomplete is already working, if you run your project you’ll see a result similar to the gif at the beginning of this tutorial, but there’s still a small problem we have to fix, note that once the suggestions list shows up you have to either erase the text from the input or select one to make the them disappear. The problem is that the user should be able to get rid of the suggestions by clicking anywhere other than the list, and to be able to do that we have to detect clicks outside the our component, more info here.

Remember the elementRef variable we saw in the constructor? Now we’re going to use it, the follwoing method tells whether the click occured in the component or outside it:

handleClick(event){
   var clickedComponent = event.target;
   var inside = false;
   do {
       if (clickedComponent === this.elementRef.nativeElement) {
           inside = true;
       }
      clickedComponent = clickedComponent.parentNode;
   } while (clickedComponent);
    if(!inside){
        this.filteredList = [];
    }
}

The function just gets the clicked component, navigates between its parent nodes and checks if one of them is the elementRef.nativeElement (which is our component), if it’s true the items from the filteredList are deleted in order to make the list disappear. Now we have to bind this method to the (document:click) event, this is how you can do it:

@Component({
    selector: 'autocomplete',
    host: {
        '(document:click)': 'handleClick($event)',
    },
    template: //TEMPLATE CODE HERE
})

That’s it!! As I said if you need more features or if you have any doubts just leave a comment, I’ll be glad to help!