11

I'm creating an ajax search page which will consist of a search input box, series of filter drop-downs and then a UL where the results are displayed.

As the filters part of the search will be in a separate place on the page, I thought it would be a good idea to create a Service which deals with coordinating the inputs and the ajax requests to a search server-side. This can then be called by a couple of separate Controllers (one for searchbox and results, and one for filters).

The main thing I'm struggling with is getting results to refresh when the ajax is called. If I put the ajax directly in the SearchCtrl Controller, it works fine, but when I move the ajax out to a Service it stops updating the results when the find method on the Service is called.

I'm sure it's something simple I've missed, but I can't seem to see it.

Markup:

<div ng-app="jobs">
    <div data-ng-controller="SearchCtrl">
        <div class="search">
            <h2>Search</h2>
            <div class="box"><input type="text" id="search" maxlength="75" data-ng-model="search_term" data-ng-change="doSearch()" placeholder="Type to start your search..." /></div>
        </div>
        <div class="search-summary">
            <p><span class="field">You searched for:</span> {{ search_term }}</p>
        </div>
        <div class="results">
            <ul>
                <li data-ng-repeat="item in searchService.results">
                    <h3>{{ item.title }}</h3>
                </li>
            </ul>
        </div>
    </div>
</div>

AngularJS:

var app = angular.module('jobs', []);

app.factory('searchService', function($http) {
    var results = [];

    function find(term) {
        $http.get('/json/search').success(function(data) {
            results = data.results;
        });
    }

    //public API
    return {
            results: results,
            find: find
    };
});

app.controller("SearchCtrl", ['$scope', '$http', 'searchService', function($scope, $http, searchService) {
    $scope.search_term = '';
    $scope.searchService = searchService;

    $scope.doSearch = function(){
        $scope.searchService.find($scope.search_term);
    };

    $scope.searchService.find();
}]);

Here is a rough JSFiddle, I've commented out the ajax and I'm just updating the results variable manually as an example. For brevity I've not included the filter drop-downs.

http://jsfiddle.net/XTQSu/1/

I'm very new to AngularJS, so if I'm going about it in totally the wrong way, please tell me so :)

Dan Atkinson
  • 11,391
  • 14
  • 81
  • 114
Steve Holland
  • 619
  • 1
  • 12
  • 29

2 Answers2

29

In your HTML, you need to reference a property defined on your controller's $scope. One way to do that is to bind $scope.searchService.results to searchService.results once in your controller:

$scope.searchService.results = searchService.results;

Now this line will work:

<li data-ng-repeat="item in searchService.results">

In your service, use angular.copy() rather than assigning a new array reference to results, otherwise your controller's $scope will lose its data-binding:

var new_results = [{ 'title': 'title 3' }, 
                   { 'title': 'title 4' }];
angular.copy(new_results, results);

Fiddle. In the fiddle, I commented out the initial call to find(), so you can see an update happen when you type something into the search box.

Mark Rajcok
  • 362,217
  • 114
  • 495
  • 492
  • 2
    Thanks Mark. It was the `angular.copy` part which was the missing piece of the puzzle. – Steve Holland Mar 12 '13 at 09:40
  • 1
    This makes total sense to me now. I had it working on 3 other services/controllers but they were just arrays that I was pushing to so the binding never broke. If you completely replace the array/object you need to use the copy! Thanks! – LukeP Oct 13 '14 at 22:19
1

The problem is that you're never updating your results within your scope. There are many approaches to do this, but based on your current code, you could first modify your find function to return the results:

function find(term) {
    $http.get('/json/search').success(function(data) {
            var results = data.results;

    });
    //also notice that you're not using the variable 'term' 
    //to perform a query in your webservice
    return results;
}

You're using a module pattern in your 'public API' so your searchService returns the find function and an array of results, but you'd want to make the function find to be the responsible for actually returning the results.

Setting that aside, whenever you call doSearch() in your scope, you'd want to update the current results for those returned by your searchService

$scope.doSearch = function(){
    $scope.searchService.results = searchService.find($scope.search_term);
};

I updated your jsfiddle with my ideas, is not functional but i added some commments and logs to help you debug this issue. http://jsfiddle.net/XTQSu/3/

odiseo
  • 6,754
  • 2
  • 20
  • 21
  • Thanks for your efforts. In this instance, I didn't want the `find` returning the results, because the results are needed in one controller, whilst the `find()` might be called from another controller. That was the reason for keeping the results local to the Service rather than in the Controller. What was missing was the binding of the results in the Service to the Controller where they are used. As Mark pointed out I had no bound reference, so that's why it wasn't updating. Using `angular.copy()` kept the binding intact when I updated the results. Again, thanks for taking the time to answer. – Steve Holland Mar 12 '13 at 09:47