Continuing to write about some Angular features I’ve been working with recently, I thought it would be interesting to show off one of the little conveniences I was able to put together using just a couple of Angular’s core features. This example leverages basically two basic features of Angular and two HTML5 features to create an easy, live autocomplete widget.
The pieces we’re going to use are:
- Angular models This demo shows how easy it is to use Angular’s models. While they can get more complicated, this example shows the base case of simply binding to a variable in scope, creating dynamic links that update the UI when the data is updated with minimal or no JavaScript interaction.
- ng-repeat Obviously if you’ve worked with Angular for any length of time you’ll be familiar with this directive. Here, it’s used here to iterate over a collection and create a dynamic
datalist
element.
- datalist and list The
datalist
element and the list
attribute are the two new HTML features that bind up the autocomplete functionality. In short, the datalist
element defines a list of autocomplete entries. The list
attribute binds the datalist
to an input
using an id
reference. This combination is available in Chrome, Opera and Firefox with buggy support in IE10+
Let’s see how it all goes together.
First up, since this is Angular, let’s look at the markup.
To get started you need to define an Angular controller. This one is named MainCtrl
. Since this is just a simplified demo to illustrate the autocomplete widget, it doesn’t do much, but the code it’s pulled from is a form used to add information about individual comic books to a simple database. Many of those comics are from the same titles (Amazing Spider-Man, Action Comics, etc.) so it’s handy to have an autocmplete list in order to improve data entry efficiency.
The input
itself has two pieces to note, the data-ng-model
attribute and the list
attribute.
Note: As always, I append data-
to Angular attributes in order to follow the HTML spec’s directions on custom attributes. That’s just me. You might want to let it all hang out. Fair play.
The data-ng-model
attribute defines the model for the input
as the title
property of variable named item
bound to the controller’s scope. Interestingly, this variable doesn’t exist at run-time. Instead of erroring with a warning about item
being undefined
Angular handles the creation of it once the form is submitted. This is a great example of how casual Angular is with their models. There’s no ceremony here. Add the data-ng-model
attribute and the binding is done for you. If the variable exists in scope, the input
is bound to it. If it doesn’t exist, Angular creates it. That’s pretty sweet.
The list
attribute references the datalist
element that the input should look for in order to build value hints. The value of the attribute is the id
of the datalist
you want to use.
The button at the end of the form is the next interesting piece of markup. On that input
we’re using data-ng-click
in order to bind the addItem
function to click events on the form. Angular will look for addItem
in the global namespace and in the controller’s scope. As you’ll see in the JavaScript example, we’re safely binding the method to the controller’s scope. If you click this button after adding a new title the title will be immediately added to the autocomplete list.
Finally, there’s the datalist
element which uses data-ng-repeat
to build out the list of autocomplete options. This directive is bound to the titles
property of the controller scope. Since Angular models are bidirectional, once you update $scope.titles
this list will be updated with new values. Pretty sweet. As you can see, this datalist
element has the id
we referenced earlier in the list
attribute of the input
.
<body data-ng-controller="MainCtrl">
<form>
<div>
<label>title</label>
<input type="text" data-ng-model="item.title" list="comicstitle">
</div>
<div>
<input type="Button" value="Add" data-ng-click="addItem(item)">
</div>
</form>
</div>
<datalist id="comicstitle">
<option data-ng-repeat="ttl in titles" value="{{ttl}}">
</datalist>
</body>
Now that you’ve seen the markup, let’s look at the JavaScript that binds it all together. It’s actually pretty simple. Most of the wiring is done in the markup so the JavaScript is pretty sparse. That said, there are some interesting things so let’s look at it in-depth.
For starters we’re defining the starter list of titles as a variable on the controller’s scope. It’s a simple array bound to $scope.titles
.
Have I mentioned that data binding is one of my favorite things about Angular? There’s absolutely no ceremony with creating a model. It’s just a variable on the controller’s scope (or even the global scope if you’re super lazy.)
After the model, the only other thing on the controller is a function called addItem
.
This function is the method referenced in the data-ng-click
attribute in the markup. It does a couple of things. The first is that is pushes the new value to $scope.titles
. This example uses lodash’s contains function to test whether or not the title already exists in the array before it’s added. That keeps the list unique.
The next bit is another neat illustration of Angular models. Since the item
we created when we added a title, setting it the object to an empty object is a shortcut to clearing the values out of the form. In this case there’s just the one form element, but if there were fifty or five hundred form elements with their data-ng-model
attributes bound to properties of item
this single line would clear all the values out of every bound form element.
app.controller( 'MainCtrl' , function( $scope ) {
$scope.titles = [ "Action Comics" , "Detective Comics" , "Superman" , "Fantastic Four" , "Amazing Spider-Man" ];
$scope.addItem = function( item ) {
if ( !_.contains( $scope.titles , item.title ) ){
$scope.titles.push( item.title );
}
$scope.item = {};
}
});
Check it out, in action, with this plunker.
And there you have it. Let me know if you have any questions, comments or corrections in the comments.