I think it's neater to use the inline notation for filter, which avoids this problem:
<div ng-repeat="animal in species | filter:query | orderBy:orderProp")>
NOTE: you can then remove the code that sets up the $watch on query
EDIT: as you specifically want it in the controller:
The reason your filter is initialising to blank, is because your $scope.species data is being populated asynchronously. When the first $watch is triggered, there is no data to filter. This stays the case until you input a query.
To solve this, set up the $watch after the data has arrived, like so:
$http.get('species.json').success(function(data) {
$scope.species = data;
$scope.$watch('query', function (query) {
$scope.filteredData = $filter('filter')($scope.species, query);
});
});
Alternatively you could manually run the filter function once, inside the success callback.