angularjs change in model renders only on clicking somewhere

Viewed 68

I am working on a search suggestion functionality using firebase and angular js. Basically on key up of a search input box, I call the below function:

scope.suggestString = {};

    scope.search = function(){
        scope.suggestString = {};

        firebaseDb.ref("users")
        .orderByChild("Name")
        .startAt(scope.searchedString)
        .endAt(scope.searchedString + "\uf8ff")
        .on("value", function(snapshot) {
            snapshot.forEach(function(childSnapshot) {
                if(scope.suggestString.hasOwnProperty(childSnapshot.key) == false){
                    scope.suggestString[childSnapshot.key] = childSnapshot;
                }
            });
        });
    }

HTML:

<form>
    <input type="text" ng-model="searchedString" ng-keyup="search()">                   

    <ul>
       <li ng-repeat="(key,item) in suggestString">
         <a href="#/home/{{key}}">{{item.val().firstName}}</a>
      </li> 
    </ul>

</form>

The code works , the call goes to firebase and fetches the records but I have to click somewhere in search input box for the suggestions to be displayed. I tried using scope.$apply() but its not working .Its says already applying scope

3 Answers

maybe you can use something like:

<input ng-model="searchedString" ng-model-options="{debounce: 1000}" type="text"> 

on input box, which will update ng-model (searchString) with search string in input element, after 1 second delay of typing.

After that you can put something like:

scope.$watch('searchedString', (newVal,oldVal)=>{
     scope.suggestString = {};

        if(scope.searchedString.length <3){
            return;
        }

        firebaseDb.ref("users")
        .orderByChild("Name")
        .startAt(scope.searchedString)
        .endAt(scope.searchedString + "\uf8ff")
        .on("value", function(snapshot) {
            snapshot.forEach(function(childSnapshot) {
                if(scope.suggestString.hasOwnProperty(childSnapshot.key) == false){
                    scope.suggestString[childSnapshot.key] = childSnapshot;
                }
            });
        });
});

Now it should work if your code for getting data is correct.

Angular doesn't know about this async call, so you have to trigger a digest. You can do that safely without getting a console error by calling $evalAsync after you get the response:

// ...
.on("value", function (snapshot) {
  scope.$evalAsync(function () {
    snapshot.forEach(function (childSnapshot) {
      if (scope.suggestString.hasOwnProperty(childSnapshot.key) == false) {
        scope.suggestString[childSnapshot.key] = childSnapshot;
      }
    })
  });
});

But I suspect scope.$apply will be fine too, if you do it outside the loop as I am doing above.

Another option is to wrap all of this firebase functionality into a separate function (possibly in a service), and return a promise from that function using the $q service, and resolve the firebase response. Then you won't have to manually trigger a digest, since Angular knows about the $q service.

Safe apply was the thing I was looking for, the search is smooth now.

Related