AngularUI-Bootstrap Typeahead: Grouping results

10,335

Solution 1

I used to have a similar requirement and here is how I did it that time.

Example Plunker: http://plnkr.co/edit/zujdouvB4bz7tFX8HaNu?p=preview

The trick is to set the typeahead-template-url to a custom item template:

<input type="text" class="form-control" placeholder="Users loaded from local database"
    ng-model="selectedUser"
    typeahead="user as user.name for user in getUsers($viewValue)"
    typeahead-template-url="typeahead-item.html" />

The item template, this represent each item in a dropdown:

<div class="typeahead-group-header" ng-if="match.model.firstInGroup">Desc {{match.model.group}}</div>
<a>
  <span ng-bind-html="match.label | typeaheadHighlight:query"></span>
</a>

As you can see, there is an ng-if to show a group header if that item has a property firstInGroup set to true.

The firstInGroup properties are populated like this using lodashjs:

$scope.getUsers = function (search) {
  var filtered = filterFilter(users, search);

  var results = _(filtered)
    .groupBy('group')
    .map(function (g) {
      g[0].firstInGroup = true; // the first item in each group
      return g;
    })
    .flatten()
    .value();

  return results;
}

Hope this fit to your requirement too.

Solution 2

please see here http://plnkr.co/edit/DmoEWzAUHGEXuHILLPBp?p=preview

instead of creating new objects here:

 angular.forEach(res.data, function(item) {
                users.push(item.UserName + " - " + item.UserDepartment);
            });

use create template :

 <script type="text/ng-template" id="customTemplate.html">
    <a> {{ match.model.name}} - department : {{match.model.dept}}</a>
  </script>

and use it in your Typeahead directive

<input type="text" ng-model="selected"
 typeahead="user.name as user for user in users | filter:$viewValue | limitTo:8" class="form-control"
typeahead-template-url="customTemplate.html">
Share:
10,335
Pramod Karandikar
Author by

Pramod Karandikar

Updated on June 05, 2022

Comments

  • Pramod Karandikar
    Pramod Karandikar about 2 years

    I am implementing typeahead using AngularUI-Bootstrap. I need to show the results grouped based on some values coming from the database. Here's a sample scenario

    1. There are some users in the database, each user has a "Department". One user name can be available in multiple departments.
    2. The end-user types in the names to search users from the database and retrieves the list in the typeahead list. Since one user name can belong to multiple departments, the requirement is to show the user names grouped by different departments. Something like this: enter image description here
    3. Then the user can select the desired user name and proceed.

    As per the Typeahead documentation present here, I don't see any option to cater to my requirement.

    I have tried the this workaround: Whenever the typeahead array is getting formed, I appended the user department to the array element:

    $scope.fetchUsers = function(val) {
            console.log("Entered fetchUsers function");
            return $http.get("http://localhost:8080/TestWeb/users", {
                params : {
                    username : val
                }
            }).then(function(res) {
                console.log("Response:",res);
                var users = [];
                angular.forEach(res.data, function(item) {
                    users.push(item.UserName + " - " + item.UserDepartment);
                });
                console.log("users=",users);
                return users;
            });
        };
    

    This way, at least the end user sees the department. But when I select the record, the selected value is the full content of the array element. Below is sample screenshot to elaborate:

    HTML

    Users from local service
    <pre>Model: {{userList | json}}</pre>
    <input type="text" ng-model="userList" placeholder="Users loaded from local database" 
    typeahead="username for username in fetchUsers($viewValue)" 
    typeahead-loading="loadingUsers" class="form-control">
    <i ng-show="loadingUsers" class="glyphicon glyphicon-refresh"></i>
    

    User types in the string

    Search result

    User selects one record

    user selects one record

    I want to avoid the department (in this case, string - Desc 4 ) when user selects a record.

    Is there any way I can achieve this grouping without any workaround? Or is there any way I can enhance my workaround?

  • Pramod Karandikar
    Pramod Karandikar almost 10 years
    It's a really nice solution. Upvoted :), but haven't accepted the answer since I am looking for actual grouping.
  • Abhishek
    Abhishek about 9 years
    Hey @runTarm, i don't understand the working of var filtered = filterFilter(users, search); var results = _(filtered) . What is the working of this and how to use this, Please help me.
  • Anil Kumar Pandey
    Anil Kumar Pandey about 9 years
    I am getting this issue in console after adding the underscore library. TypeError: _(...).groupBy(...).map is not a function, please give some explanation about _(filtered) things.
  • sunsay
    sunsay almost 9 years
    great solution! There is a small bug - we need to copy users list before filtering: var filtered = filterFilter(angular.copy(users), search); plnkr.co/edit/gdxBToatIHMFbH7e6QzA?p=preview
  • Anil Kumar Pandey
    Anil Kumar Pandey over 8 years
    any solution for this problem TypeError: _(...).groupBy(...).map is not a function Parser.functionCall (angular.js:6) at OPERATORS.| (angular.js:5) at Object.Parser.binaryFn.extend.constant [as source] (angular.js:5) at link.getMatchesAsync (ui-bootstrap-tpls.js:3) Please help me to out of it. its happening while switching the tabs in my website, but works fine after page load.
  • Hirbod
    Hirbod over 8 years
    I had the same problems. I managed to fix var results = _.groupBy(myArray, 'group'); results = _.map(results, function(g) { g[0].firstInGroup = true; // the first item in each group return g; }); results = _.flatten(results); return results; I don't need .value, and if you need it, just add results = _.values(results);at the end, before returning
  • Vincent van der Weele
    Vincent van der Weele over 8 years
    Regarding the questions above: underscore != lodash. If you use underscore, you should call _.chain(filtered) to start the chain. Note that this is not entirely equivalent to _(filtered), since the latter is lazy. In this case we use all values anyway, so it doesn't matter.
  • Deepak Rai
    Deepak Rai almost 8 years
    @PramodKarandikar , Did you find what you were searching for ? Grouping results in typeahead . If you could share your solution here that would be great.