4

I'm using Angular Material md-autocomplete in my project. In that I'm getting the suggest listing from the Service Host via ajax call using $http service.

Issue: $http issue - Values can't be returned before a promise is resolved in md-autocomplete Angular Material

My Requirement: I need an updated Suggestion List using remote data sources in md-autocomplete Angular Material - Ajax $http service.

I used the approach as mentioned in Angular Material link https://material.angularjs.org/latest/demo/autocomplete

Source Code:

Scenario 1:

HTML Source Code:

<md-autocomplete flex required
    md-input-name="autocompleteField"
    md-no-cache="true"
    md-input-minlength="3"
    md-input-maxlength="18"
    md-selected-item="SelectedItem"
    md-search-text="searchText"
    md-items="item in querySearch(searchText)"
    md-item-text="item.country" Placeholder="Enter ID" style="height:38px !important;">
    <md-item-template>
        <span class="item-title">
            <span md-highlight-text="searchText" md-highlight-flags="^i"> {{item.country}} </span>
    </md-item-template>
</md-autocomplete>

AngularJS Script:

//bind the autocomplete list when text change
function querySearch(query) {
    var results = [];
    $scope.searchText = $scope.searchText.trim();
    if (query.length >=3) {
        results = LoadAutocomplete(query);
    }
    return results;
}

//load the list from the service call
function LoadAutocomplete(id) {
    var countryList = [];
    $http({
            method: "post",
            url: "https://www.bbminfo.com/sample.php",
            params: {
                token: id
            }
        })
        .success(function (response) {
            countryList = response.records;
        });

        return countryList;
}

Scenario 2:

HTML with AngularJS Source Code:

<!DOCTYPE html>
<html>
<link rel="stylesheet" href="http://ajax.googleapis.com/ajax/libs/angular_material/1.0.0/angular-material.min.css">
    <script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular-animate.min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular-aria.min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular-messages.min.js"></script>

<!-- Angular Material Library -->
<script src="http://ajax.googleapis.com/ajax/libs/angular_material/1.0.0/angular-material.min.js"></script>
<body>

<div ng-app="myApp" ng-controller="myCtrl"> 

<p>Person to Select:</p>

<md-autocomplete
          ng-disabled="isDisabled"
          md-no-cache="noCache"
          md-selected-item="selectedItem"
          md-search-text-change="searchTextChange()"
          md-search-text="searchText"
          md-selected-item-change="selectedItemChange(item)"
          md-items="item in Person"
          md-item-text="item.Name"
          md-min-length="0"
          placeholder="Which is your favorite Person?">
        <md-item-template>
          <span md-highlight-text="ctrl.searchText" md-highlight-flags="^i">{{item.country}}</span>
        </md-item-template>
        <md-not-found>
          No Person matching "{{searchText}}" were found.
        </md-not-found>
      </md-autocomplete>
      <br/>
</div>

<script>
    var app = angular.module('myApp', ['ngMaterial']);

    app.controller('myCtrl', function ($scope, $http, $q) {

        $scope.searchText = "";
        $scope.Person = [];
        $scope.selectedItem = [];
        $scope.isDisabled = false;
        $scope.noCache = false;

        $scope.selectedItemChange = function (item) {
            alert("Item Changed");
        }
        $scope.searchTextChange = function () {

            $http({
                method: "POST",
                url: "https://www.bbminfo.com/sample.php",
                params: {
                    token: $scope.searchText
                }
            })
            .success(function (response) {
                $scope.Person = response.records;
            });
        }

    });
</script>
</body>
</html>

In Scenario 1, I used the function to fetch the filtered list md-items="item in querySearch(searchText)". But in Scenario 2, I used a $scope variable md-items="item in Person"

Kindly refer the Snapshots

Snapshot 1:

Autocomplete Listing Issue - UI

Here I'm searching for indian but it shows the result for india. I debugged the issue in Firefox Browser Firebug, see the above Snapshot 1 it shows, the request was sent for the search term indian via POST Method and I got the response of one matching items as a JSON Object successfully, which is shown in the bottom of SnapShot 1

The issue I find out in this case, the Values can't be returned before a promise is resolved

Steps I tried:

Case 1: I used the AngularJS filter in UI md-items="item in Person | filter: searchText", it gives the filtered list of previously fetched remote data not a currently fetched remote data. While on Backspacing the character in the textbox it shows the improper suggestion list.

Case 2: I tried to update the changes in UI by calling $scope.$apply() within a $http service, but it fails. Because $http service calling the $scope.$apply() by default, show it throws me an error Error: [$rootScope:inprog].... Finally in this attempt I failed.

Case 3: I created a function, within the function I'm manually called the $scope.$apply(), within the function I manually pushed and poped one dummy item to the $scope variable which is bind in the md-autocomplete. But I failed in this attempt. Because here also I got a same output as same as in the snapshot.

function Ctrlm($scope) {
    $scope.messageToUser = "You are done!";
    setTimeout(function () {
        $scope.$apply(function () {

            $scope.dummyCntry = [
                {
                    sno: 0,
                    country: ""
                },
            ];

            $scope.Person.push($scope.dummyCntry);

            var index = $scope.Person.indexOf($scope.dummyCntry);
            $scope.Person.splice(index, 1);

        });
    }, 10);
}

Case 4: I did the same approach as like "Case 3" within the $scope.$watchCollection. Here also I got a setback.

$scope.$watchCollection('Person', function (newData, oldDaata) {
    $scope.dummyCntry = [
                {
                    sno: 0,
                    country: ""
                },
            ];

    newData.push($scope.dummyCntry);

    var index = newData.indexOf($scope.dummyCntry);
    newData.splice(index, 1);
});

Case 5: Instead of $http service, I used the jquery ajax call. In that I used the $scope.apply() to update the UI manually. I failed in this attempt once again, here also I got the same output.

$scope.searchTextChange = function () {
    if (($scope.searchText != undefined) && ($scope.searchText != null)) {

        $.ajax({
            type: 'GET',
            url: "https://www.bbminfo.com/sample.php?token=" + $scope.searchText,
            success: function (response) {
                $scope.$apply(function () {
                    $scope.Person = response.records;
                });
            },
            error: function (data) {
                $scope.$apply(function () {
                    $scope.Person = [];
                });
            },
            async: true
        });


    } else {
        $scope.Person = [];
    }
}

In all the attempts I can't able to fix the issue.

@georgeawg https://stackoverflow.com/users/5535245/georgeawg suggested me to post a new question, he stated, "write a new question that describes what you are actually trying to accomplish, include the desired behavior, a summary of the work you've done so far to solve the problem, and a description of the difficulty you are having solving it."

Reference Questions which I was posted in the earlier

Post 1: http://www.stackoverflow.com/questions/35624977/md-items-is-not-updating-the-suggesion-list-properly-in-md-autocomplete-angular

Post 2: http://www.stackoverflow.com/questions/35646077/manually-call-scope-apply-raise-error-on-ajax-call-error-rootscopeinprog

My Requirement: I need an updated Suggestion List using remote data sources in Angular Material md-autocomplete - Ajax $http service.

Kindly assist me in this regards.

For Testing Purpose Use the following Source Code

Use the following URL for Remote Data Source: https://bbminfo.com/sample.php?token=ind

The Remote Data Source URL contains the List of Countries Name.

Directly Test the Code by click the below Run Code Snippet button.

Complete HTML with AngularJS Source Code:

<!DOCTYPE html>
<html>
<link rel="stylesheet" href="http://ajax.googleapis.com/ajax/libs/angular_material/1.0.0/angular-material.min.css">
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular-animate.min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular-aria.min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular-messages.min.js"></script>

<!-- Angular Material Library -->
<script src="http://ajax.googleapis.com/ajax/libs/angular_material/1.0.0/angular-material.min.js"></script>
<body>

<div ng-app="myApp" ng-controller="myCtrl"> 

<p>Country to Select:</p>
<md-content>
<md-autocomplete
          ng-disabled="isDisabled"
          md-no-cache="noCache"
          md-selected-item="selectedItem"
          md-search-text-change="searchTextChange()"
          md-search-text="searchText"
          md-selected-item-change="selectedItemChange(item)"
          md-items="item in Person"
          md-item-text="item.country"
          md-min-length="0"
          placeholder="Which is your favorite Country?">
        <md-item-template>
          <span md-highlight-text="searchText" md-highlight-flags="^i">{{item.country}}</span>
        </md-item-template>
        <md-not-found>
          No Person matching "{{searchText}}" were found.
        </md-not-found>
      </md-autocomplete>
      </md-content>
      <br/>
</div>

<script>
    var app = angular.module('myApp', ['ngMaterial']);

    app.controller('myCtrl', function ($scope, $http, $q) {

        $scope.searchText = "";
        $scope.Person = [];
        $scope.selectedItem = [];
        $scope.isDisabled = false;
        $scope.noCache = false;

        $scope.selectedItemChange = function (item) {
            alert("Item Changed");
        }
        $scope.searchTextChange = function () {

            $http({
                method: "post",
                url: "https://www.bbminfo.com/sample.php",
                params: {
                    token: $scope.searchText
                }
            })
            .success(function (response) {
    $scope.Person = response.records;
            });
        }

    });
</script>
</body>
</html>
Community
  • 1
  • 1
B.Balamanigandan
  • 4,713
  • 11
  • 68
  • 130
  • I know this dupe won't help you, but it's an exact dupe. http://stackoverflow.com/questions/14220321/how-do-i-return-the-response-from-an-asynchronous-call/14220323 You're trying to return from an asynchronous callback, and that isn't possible. You instead simply need to return a promise, then consume that promise in the controller using .then and a callback to update the scope. – Kevin B Feb 29 '16 at 16:23
  • Here's an even more specific dupe: http://stackoverflow.com/questions/29512238/material-design-angular-md-autocomplete-remote and here's a related github issue: https://github.com/angular/material/issues/1798 Please, do your research. – Kevin B Feb 29 '16 at 16:27
  • @KevinB - Your link is for jQuery. I accept the concept is same but here its different. Because in angularjs it calls the $scope.apply to update the UI by default even-though in $http service. In the https://material.angularjs.org/latest/demo/autocomplete they are specifically mentioned "Use md-autocomplete to search for matches from local or remote data sources." But its not working properly for remote data sources. – B.Balamanigandan Feb 29 '16 at 16:31
  • Your first solution is the closest to working solution. You simply need to `return $http(...)` rather than the value at the end and remove the callback – Kevin B Feb 29 '16 at 16:31
  • @KevinB Can you please provide you solution please... – B.Balamanigandan Feb 29 '16 at 16:33
  • My solution would be a duplicate of http://stackoverflow.com/questions/29512238/material-design-angular-md-autocomplete-remote but without the .then() – Kevin B Feb 29 '16 at 16:33
  • @KevinB http://stackoverflow.com/questions/29512238/material-design-angular-md-autocomplete-remote - in this post he asking how to fetch from remote source, not similar to this post. – B.Balamanigandan Feb 29 '16 at 16:34
  • ... you ARE fetching from a remote source... what do you think $http does? your server IS a remote source. – Kevin B Feb 29 '16 at 16:34
  • @KevinB Fetching from remote source is not an issue here, its not updating in the UI... I tried the approach as in http://stackoverflow.com/questions/29512238/material-design-angular-md-autocomplete-remote but its also returning the same. – B.Balamanigandan Feb 29 '16 at 16:35
  • Try it again and provide what went wrong, because that's the solution. All these other attempts are simply wrong. – Kevin B Feb 29 '16 at 16:36
  • @KevinB nothing is wrong. It also gives the same output as mentioned in my post. – B.Balamanigandan Feb 29 '16 at 16:37
  • @KevinB Please help me in this regards... I need an update suggestion listing from the remote source... – B.Balamanigandan Feb 29 '16 at 16:42
  • Returning a promise is the solution. If that solution doesn't work, either your data structure is incorrect, or there's some bug in the code you're using. Not much i or anyone here can help with. – Kevin B Feb 29 '16 at 16:50
  • You could try updating your version of angular material, it's a few versions out of date, and there were changes to md-autocomplete within the past month related to promise handling. – Kevin B Feb 29 '16 at 16:53
  • @use this fiddle link https://jsfiddle.net/bbminfo/0qdbt94b/ here i posted the source code as like the post http://stackoverflow.com/questions/29512238/material-design-angular-md-autocomplete-remote – B.Balamanigandan Feb 29 '16 at 16:57
  • a fiddle isn't going to work due to CORS – Kevin B Feb 29 '16 at 16:59
  • Ok you copy the source code and paste it in your local and test it once. Last 4 days I'm working for the same. I can't able to fix this... – B.Balamanigandan Feb 29 '16 at 17:00
  • yeah sorry i'm not doing that. the solution is simple, return a promise. if that doesn't work, the problem is probably elsewhere. – Kevin B Feb 29 '16 at 17:03
  • you didn't follow the instructions correctly in the dupe question. you used .success instead of .then. it works just fine. – Kevin B Feb 29 '16 at 17:08
  • I used first .then, it won't work. after that I changed to .success – B.Balamanigandan Feb 29 '16 at 17:09
  • Check the code once. https://jsfiddle.net/bbminfo/0qdbt94b/5/ – B.Balamanigandan Feb 29 '16 at 17:11
  • `response.data` has your data. your data has a `records` key on the object. so you need `response.data.records`. all of the examples use response.data because response.data is the data that was returned, you then have to filter it. this is in the documentation. – Kevin B Feb 29 '16 at 17:14

4 Answers4

4

@KevinB https://stackoverflow.com/users/400654/kevin-b - Gives the Idea how to implement. I really thank him... Once again thanks alot Kevin...

I got the Exact solution, what I need.

The Source Code is

<!DOCTYPE html>
<html>
<link rel="stylesheet" href="http://ajax.googleapis.com/ajax/libs/angular_material/1.0.0/angular-material.min.css">
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular-animate.min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular-aria.min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular-messages.min.js"></script>

<!-- Angular Material Library -->
<script src="http://ajax.googleapis.com/ajax/libs/angular_material/1.0.4/angular-material.min.js"></script>
<body>

<div ng-app="myApp" ng-controller="myCtrl"> 

<p>Country to Select:</p>
<md-content>
<md-autocomplete
          ng-disabled="isDisabled"
          md-no-cache="noCache"
          md-selected-item="selectedItem"
          md-search-text="searchText"
          md-items="item in searchTextChange(searchText)"
          md-item-text="item.country"
          md-min-length="0"
          placeholder="Which is your favorite Country?">
        <md-item-template>
          <span md-highlight-text="searchText" md-highlight-flags="^i">{{item.country}}</span>
        </md-item-template>
        <md-not-found>
          No Person matching "{{searchText}}" were found.
        </md-not-found>
      </md-autocomplete>
      </md-content>
      <br/>
</div>

<script>
    var app = angular.module('myApp', ['ngMaterial']);

    app.controller('myCtrl', function ($scope, $http, $q, GetCountryService) {

        $scope.searchText = "";
        $scope.Person = [];
        $scope.selectedItem = [];
        $scope.isDisabled = false;
        $scope.noCache = false;

        $scope.selectedItemChange = function (item) {
            //alert("Item Changed");
        }
        $scope.searchTextChange = function (str) {
   return GetCountryService.getCountry(str);
        }

    });
 
 app.factory('GetCountryService', function ($http, $q) {
        return {
            getCountry: function(str) {
                // the $http API is based on the deferred/promise APIs exposed by the $q service
                // so it returns a promise for us by default
    var url = "https://www.bbminfo.com/sample.php?token="+str;
                return $http.get(url)
                    .then(function(response) {
                        if (typeof response.data.records === 'object') {
                            return response.data.records;
                        } else {
                            // invalid response
                            return $q.reject(response.data.records);
                        }

                    }, function(response) {
                        // something went wrong
                        return $q.reject(response.data.records);
                    });
            }
        };
    });
</script>
</body>
</html>

I Briefly explained about md-autocomplete in the following blog - http://www.increvcorp.com/usage-of-md-autocomplete-in-angular-material/

Community
  • 1
  • 1
B.Balamanigandan
  • 4,713
  • 11
  • 68
  • 130
1

I struggled with this as well for a bit. Basically you should actually be returning a promise that returns the content.

This is my "search" function

$scope.searchData = function (searchTxt) {
        return $http.get('/TestSearch', { params: { searchStr: searchTxt } })
            .then(function(response) {

                return response.data;
            });
    };

Also I'm not sure what version of angular you're running but I think .success was deprecated.

And here is my md-autocomplete as well

<md-autocomplete placeholder="Text goes here"
                 md-selected-item="vm.autocomp"
                 md-search-text="searchText"
                 md-items="item in searchData(searchText)"
                 md-item-text="item">
    <span md-highlight-text="searchText">{{item}}</span>
</md-autocomplete>

EDIT1: Sorry original JS was in TypeScript. Fixing that now

cDecker32
  • 813
  • 1
  • 10
  • 20
1

The Answer Marked is Correct.

  • .then() - full power of the promise API but slightly more verbose
  • .success() - doesn't return a promise but offers slightly more convenient syntax
0

Why not just put return countryList inside the success function.

function LoadAutocomplete(id) {
    var countryList = [];
    $http({
            method: "post",
            url: "https://www.bbminfo.com/sample.php",
            params: {
                token: id
            }
        })
        .success(function (response) {
            countryList = response.records;
            return countryList;
        })
        .error(function (response) {
            countryList = [];
            return countryList;
        });
}

edit due to deprecation of .success and .error methods:

function LoadAutocomplete(id) {
    var countryList = [];
    $http({
            method: "post",
            url: "https://www.bbminfo.com/sample.php",
            params: {
                token: id
            }
        })
        .then(function (response) {
            countryList = response.data.records;
            return countryList;
        },function () {
            countryList = [];
            return countryList;
        });
}
  • The `.success` and `.error` methods **ignore** return values. This is the reason those methods were deprecated. https://docs.angularjs.org/api/ng/service/$http#deprecation-notice – georgeawg Mar 05 '16 at 10:35
  • hi @georgeawg I just copied the source code above. but you are right those two methods are already deprecated. – theultimateknowhow.com Mar 09 '16 at 06:27