I am writing a Type-ahead / Autocomplete search with angular using directive and filter service. I am almost there but need your expert opinions on things which I am planning to achieve.
@Todos:
Look and feel
Allow navigation using keyboard
- Enter and Esc key handling
Could anyone review and suggest more optimize ways of writing this?
Also, shall I bind events within a parent controller scope or directive scope? Here is how my directive looks like:
var modServiceDef = angular.module('modSerDefinition', []);
modServiceDef.controller('modDefCtrl', ['$scope', 'myFactory', function($scope, myFactory) {
myFactory.getData().then(function (httpData) {
$scope.dataToPopulate = httpData;
},function(httpData) {
console.log('name retrieval failed.');
});
}
]);
modServiceDef.directive('typeAhead',[function() {
return {
scope : {
cinfo:'=datalist',
},
link:function(scope,element,attr) {
scope.visible = true;
scope.updateSearchTerm = function(selName) {
scope.sterm = selName;
}
},
templateUrl:'templates/typeahead.tmpl'
}
}]);
modServiceDef.factory('myFactory', function($http,$q) {
var factory = {};
var def = $q.defer();
factory.getData = function() {
$http.get('https://raw.githubusercontent.com/dominictarr/random-name/master/first-names.json')
.success(function(res){
factory.data = res;
def.resolve(res);
})
.error(function(err){
def.reject(err);
})
return def.promise;
}
return factory;
});
HTML:
<type-ahead datalist="dataToPopulate"></type-ahead>
Template:
<input type='search' id='idSearch' ng-model='sterm' placeholder='Enter Search Term' />
<ul ng-if='sterm.length'>
<a href='#'>
<li ng-model='selName' ng-click='updateSearchTerm(data)' ng-repeat="data in cinfo| filter:sterm as results track by $index " class='listdata'>
{{data}}
</li>
</a>
</ul>
2 Answers 2
There are some points you could improve:
- App structure:
I would put the typeahead directive in an own module that you can include as a dependency to your app.
angular.module('demoApp', ['pdTypeAhead'])
. - Click events: I think the best place is in the directive controller because they're there for your typeahead logic.
- Keyboard binding: You can add the mousetrap.js keyboard binding in a directive and add it in your typeahead
link
method. It's a bit tricky because you need to change the typeahead restrict to attribute (otherwise there is a problem with infinite digest loop.). Your todos:
- look & feel: Twitter Bootstrap is a good starting point to do your styling.
- Keyboard navigation: You could do it with pure js but I think mousetrap.js is easier to use.
- Enter + Escape handling: Also use mousetrap. For event handling you could use
$scope.$brodcast('eventName', data)
and catch it in your directive's controller with$scope.$on('eventName', function(data) { ... });
- Responsiveness: If you'd type the letter
a
in your code it took pretty long to show the result (> 3000 matches). I think it's OK to limit the result to 100 or lower matches with alimitTo
filter and the result is immediately visible.
You can find my code in this fork. (If you like I could do a pull request in your repo.)
There are still the following possible improvements in the updated code:
- Controller of the typeahead directive could be in it's own file. Improves readability.
- It's probably possible to put code from the typeahead controller into a service for refactoring.
- Escape key handling could be improved: After a click on a name you can't undo that click with a press on escape. But if you select with enter you can undo with escape.
- Directive template
typeahead.tmpl
could be moved to the components folder of the directivejs/components/typeahead
. - Typeahead controller: Use
controllerAs
instead of$scope
(if possible). Best practice, see style guide. - Check if filtering is also possible directly in
ng-repeat
? Should be possible and better. I've just moved it to js for easier debugging. - Add options to typeahead, e.g. a way to enable or disable autofocus of search input directly in markup e.g.
<div type-ahead datalist="dataToPopulate" auto-focus="off">
- Add a
config provider
for options service so you could add the options application wide inside ofangular.module(...).config(...)
. This post is helpful for creating configurable directives. - Add
ng-blur
to close the typeahead if clicked outside. - Add unit tests for the directive.
-
\$\begingroup\$ please do the pull request . i will take it up then \$\endgroup\$Robin– Robin2015年06月21日 06:17:25 +00:00Commented Jun 21, 2015 at 6:17
You could use the implicit version of deps management in Angular. It saves you a few keystrokes, just make sure the dependency variable name is the same as the dependency name. If you plan to minify, just run it through ng-annotate.
modServiceDef.controller('modDefCtrl', function($scope, myFactory) {
...
});
I'm not sure why your code appears to recycle promises in myFactory
when $http.get
returns a promise already. Also, logging the http error is the responsibility of myFactory
and not your controller, so you console.log
should be there. I suppose changing it to this would be better:
modServiceDef.factory('myFactory', function($http,$q) {
return {
getData: function() {
return $http.get('https://raw.githubusercontent.com/dominictarr/random-name/master/first-names.json')
.error(function(){
console.log('name retrieval failed.');
});
}
};
});
Other than those, I think your code is good.