TheBlackBenzKid TheBlackBenzKid - 17 days ago 8
JSON Question

How to filter Firebase JSON using AngularJS?

I am calling my Firebase Database JSON in AngularJS and trying to filter through the code. I keep seeing Array errors like:

Error: [filter:notarray] http://errors.angularjs.org/1.4.9/filter/notarray?


My HTML:

<form>
<div class="form-group">
<div class="input-group">
<div class="input-group-addon"><i class="fa fa-search"></i></div>
<input type="text" class="form-control" placeholder="Search" ng-model="searchTranslation">
</div>
</div>
</form>

<table class="table table-bordered table-striped">
<thead>
<tr>
<th>ID</th>
<th>English</th>
<th>Arabic</th>
<th>Status</th>
<th>Settings</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="i in translations | filter:searchTranslation track by $index">

<td>{{$index}}</td>
<td>{{i.transNameEn}}</td>
<td class="textarabic">{{i.transNameArabic}}</td>
<td class="{{i.transStatus}}">{{i.transStatus}}</td>
<td>
<a ng-href="{{url}}pages-edit-translation/{{i.transId}}"
class="btn caps btn-warning">
EDIT
</a>
</td>
</tr>
</tbody>
</table>


My JS code

// @pages-home-translation
cmsApp.controller('pages-home-translation', function ($scope, $http) {

$scope.sortType = 'English'; // set the default sort type
$scope.sortReverse = false; // set the default sort order
$scope.searchTranslation = ''; // set the default search/filter term

$http.get(firebase_url+'cms/translations.json'+randstatus).success(function(data) {
$scope.translations=data; // or data.data
});

});


My JSON:

{
"00hym5km2tf08s38fr-ivjgnsw6": {
"notes": "",
"transCreation": "11/15/2016, 4:14:07 PM",
"transId": "00hym5km2tf08s38fr-ivjgnsw6",
"transModified": "11/15/2016, 4:14:07 PM",
"transNameArabic": "استخدم كعنوان الدفع الافتراضي",
"transNameEn": "Use as my default billing address",
"transStatus": "FIXED"
},
"08zq3t9411zaketnpnwmi-ivjhzz5q": {
"notes": "",
"transCreation": "11/15/2016, 4:51:35 PM",
"transId": "08zq3t9411zaketnpnwmi-ivjhzz5q",
"transModified": "11/15/2016, 4:51:35 PM",
"transNameArabic": "احذية كرة القدم",
"transNameEn": "Football Shoes",
"transStatus": "FIXED"
},
"0aoycw0b0c9v8ov6xbt9-ivjhwnv6": {
"notes": "",
"transCreation": "11/15/2016, 4:49:00 PM",
"transId": "0aoycw0b0c9v8ov6xbt9-ivjhwnv6",
"transModified": "11/15/2016, 4:49:00 PM",
"transNameArabic": "جينز واسع",
"transNameEn": "Flared Jeans",
"transStatus": "FIXED"
}
}


Note: If I remove

<tr ng-repeat="i in translations | filter:searchTranslation track by $index">


and do this instead it works:

<tr ng-repeat="i in translations">


But clearly I wish to filter the results using the search form. Using
data.data
shows no errors in the console.

I have tried to do
setTimeOut
on the
http.get
and also delays so that it loads the JSON first but it is still not working.

Thanks

Answer

The problem is that the filter doesn't filter objects by default, even though ng-repeat can iterate over an object properties. Therefore there is two way out for this issue: convert the object to array before set it to your model, or you can create a custom filter (which is not much reusable) to filter over an object properties.

First

To create a custom filter you can do:

Html

<ANY ng-repeat="item in items | filterObject:myModel">

JS

angular.filter('filterObject', function () {
    return function (obj, myModel) {
        // filter logic, bla bla bla
    };
});

Second

To convert the object into an array, you can do it on controller side (or create a helper function, service, etc.) or you can create a custom filter to do so. For example:

Html

<ANY ng-repeat="item in items | asArray | filter:myModel">

JS

.filter('asArray', function() {
  return function(obj /*, addKey*/ ) {
    // in case of undefined just return the same object to pass through
    if (!obj) return obj;
    // return an object maped as array of key as an item
    return Object.keys(obj).map(function(key) {
      return obj[key];
    });
  };
});

Using a filter to conver array into an object, you can reuse the $filter logic without having to implement a filter solution again. Therefore, I consider this better than usign a custom filter for the task.

A little less expensive approach would be converting it to an array before sending it to your model. However, you may want to keep the original property names (ids or whatever that is).

$http.get(firebase_url+'cms/translations.json'+randstatus).success(function(data) {    
    var myData = Object.keys(data).map(function(key) {
        return obj[key];
    });    
    $scope.translations = myData;    
});

The following example implements this solution, using the second approach (there is 1 second delay simulating a server response or something to fill the translations model):

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

cmsApp.filter('asArray', function() {
  return function(obj /*, addKey*/ ) {
    // in case of undefined just return the same object to pass through
    if (!obj) return obj;
    // return an object maped as array of key as an item
    return Object.keys(obj).map(function(key) {
      return obj[key];
    });
  };
});

// @pages-home-translation
cmsApp.controller('pages-home-translation', function($scope, $http, $timeout) {

  $scope.sortType = 'English'; // set the default sort type
  $scope.sortReverse = false; // set the default sort order
  $scope.searchTranslation = ''; // set the default search/filter term

  var data = {
    "00hym5km2tf08s38fr-ivjgnsw6": {
      "notes": "",
      "transCreation": "11/15/2016, 4:14:07 PM",
      "transId": "00hym5km2tf08s38fr-ivjgnsw6",
      "transModified": "11/15/2016, 4:14:07 PM",
      "transNameArabic": "استخدم كعنوان الدفع الافتراضي",
      "transNameEn": "Use as my default billing address",
      "transStatus": "FIXED"
    },
    "08zq3t9411zaketnpnwmi-ivjhzz5q": {
      "notes": "",
      "transCreation": "11/15/2016, 4:51:35 PM",
      "transId": "08zq3t9411zaketnpnwmi-ivjhzz5q",
      "transModified": "11/15/2016, 4:51:35 PM",
      "transNameArabic": "احذية كرة القدم",
      "transNameEn": "Football Shoes",
      "transStatus": "FIXED"
    },
    "0aoycw0b0c9v8ov6xbt9-ivjhwnv6": {
      "notes": "",
      "transCreation": "11/15/2016, 4:49:00 PM",
      "transId": "0aoycw0b0c9v8ov6xbt9-ivjhwnv6",
      "transModified": "11/15/2016, 4:49:00 PM",
      "transNameArabic": "جينز واسع",
      "transNameEn": "Flared Jeans",
      "transStatus": "FIXED"
    }
  };

  $timeout(function() {
    $scope.translations = data; // or data.data
  }, 1500);

});

angular.element(document).ready(function() {
  angular.bootstrap(document, ['cmsApp']);
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.8/angular.js"></script>
<div ng-controller="pages-home-translation">
  <form>
    <div class="form-group">
      <div class="input-group">
        <div class="input-group-addon"><i class="fa fa-search"></i>
        </div>
        <input type="text" class="form-control" placeholder="Search" ng-model="searchTranslation">
      </div>
    </div>
  </form>

  <table class="table table-bordered table-striped" border="1" cellpadding="4" style="border-collapse: collapse;">
    <thead>
      <tr>
        <th>ID</th>
        <th>English</th>
        <th>Arabic</th>
        <th>Status</th>
        <th>Settings</th>
      </tr>
    </thead>
    <tbody>
      <tr ng-repeat="i in translations | asArray | filter:searchTranslation track by $index">

        <td>{{$index}}</td>
        <td>{{i.transNameEn}}</td>
        <td class="textarabic">{{i.transNameArabic}}</td>
        <td class="{{i.transStatus}}">{{i.transStatus}}</td>
        <td>
          <a ng-href="{{url}}pages-edit-translation/{{i.transId}}" class="btn caps btn-warning">EDIT</a>
        </td>
      </tr>
    </tbody>
  </table>
</div>