Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Knockout foreach vs Angular ng-repeat

Tags:

angularjs

What I did with Knockout and I am Trying to do with Angular.

In my current project I have a table which data is being added by scroll event. When the user scrolls down I add 20 row to the end of the table and the total row count can reach 2k-10k. I start with showing 20 record and when the user scrolls down I keep adding 20 rows until reaching the total row count.

As in my Angular fiddle example when the repeat is used if a new data is pushed to the array ''Angular" executes all the records and render them again but knockout just executes and renders the new added record. In my project because I display thousands of data in a single table this way which angular works or I think that it works kills my performance.

It have been just a few week that I am using angular I don't know If I am doing anything wrong or if I need to modify some things.

Knockoutjs Example:

<h2>Your seat reservations</h2>

<table>
    <thead><tr>
        <th>Passenger name</th><th>Meal</th><th>Test</th>
    </tr></thead>
    <tbody data-bind="foreach: seats()">
        <tr>
           <td data-bind="text: mealName"></td>
           <td data-bind="text: price"></td>
           <td data-bind="text: $root.cellAdded()"></td>
        </tr>    
    </tbody>
</table>
<button data-bind="click: $root.PushData()">I am Here</button>

JS:

// Overall viewmodel for this screen, along with initial state
function ReservationsViewModel() {
   var self = this;

   self.cellAdded = function(){
        console.log('Test Added');
        return 'ok';
    };

    self.PushData = function(){
        console.log('PushData Called');
        self.seats.push({ mealName: "Standard (sandwich)", price: 0 });
    };


    // Editable data
    self.seats = ko.observableArray([
       { mealName: "Standard (sandwich)", price: 0 },
        { mealName: "Premium (lobster)", price: 34.95 },
        { mealName: "Ultimate (whole zebra)", price: 290 }
    ]);
}

ko.applyBindings(new ReservationsViewModel());

AngularJS Example:

<div ng-app>

<div ng-controller="TodoCtrl">
<h2>Your seat reservations</h2>

<table>
    <thead><tr>
        <th>Passenger name</th><th>Meal</th><th>Test</th>
    </tr></thead>
    <tbody >
        <tr ng-repeat="seat in seats">
           <td>{{seat.mealName}}</td>
           <td>{{seat.price}}</td>
           <td>{{callMe()}}</td>
        </tr>    
    </tbody>
</table>
<button ng-click="addRow()">Add Row</button>

</div>
    </div>

JS:

function TodoCtrl($scope) {

    $scope.callMe = function(){
        console.log('Test Added');
        return 'Yes';
    };
    // initialize controller's model    
   $scope.seats = [
       { mealName: "Standard (sandwich)", price: 0 },
        { mealName: "Premium (lobster)", price: 34.95 },
        { mealName: "Ultimate (whole zebra)", price: 290 }
    ];

    // Define member functions
    $scope.addRow = function() {
        console.log('PushData Called');
        $scope.seats.push( { mealName: "Standard (sandwich)", price: 0 });

    };

}
like image 395
SamSamet Avatar asked Jan 10 '14 18:01

SamSamet


1 Answers

I suggest updating to Angular 1.3+ and use its One time binding capability, given the records do not change after being added. This minimizes the watches on your DOM by a good amount and helps improving performance.

<div ng-app>

  <div ng-controller="TodoCtrl">
    <h2>Your seat reservations</h2>

    <table>
      <thead><tr>
        <th>Passenger name</th><th>Meal</th><th>Test</th>
      </tr></thead>
      <tbody >
        <tr ng-repeat="seat in seats">
          <td>{{::seat.mealName}}</td>
          <td>{{::seat.price}}</td>
          <!--<td>{{callMe()}}</td>-->
        </tr>    
      </tbody>
   </table>
   <button ng-click="addRow()">Add Row</button>
  </div>
</div>

Also I recommend removing that function call in your expression because it's evaluated everytime Angular runs the digest cycle, which is a real performance killer.

If you want to check whether your model has updated, use $watch in your controller (https://docs.angularjs.org/api/ng/type/$rootScope.Scope).

$scope.$watch('seats', function() {
   console.log('Test Added');
});

Another hint yet not related to performance: It's good practice and helps minimizing scope issues to always use a model object of some kind in your controllers. So your controller would look like this:

function TodoCtrl($scope) {

  // initialize controller's model
  $scope.model = {};
  $scope.model.seats = [
     { mealName: "Standard (sandwich)", price: 0 },
     { mealName: "Premium (lobster)", price: 34.95 },
     { mealName: "Ultimate (whole zebra)", price: 290 }
  ];

  // Define member functions
  $scope.addRow = function() {
    console.log('PushData Called');
    $scope.model.seats.push( { mealName: "Standard (sandwich)", price: 0 });
  };

  $scope.$watch('model.seats', function() {
    console.log('Test Added');
  });
}

and your HTML like this:

<div ng-app>

  <div ng-controller="TodoCtrl">
    <h2>Your seat reservations</h2>

    <table>
      <thead><tr>
        <th>Passenger name</th><th>Meal</th><th>Test</th>
      </tr></thead>
      <tbody >
        <tr ng-repeat="seat in model.seats">
          <td>{{::seat.mealName}}</td>
          <td>{{::seat.price}}</td>
          <!--<td>{{callMe()}}</td>-->
        </tr>    
      </tbody>
   </table>
   <button ng-click="addRow()">Add Row</button>
  </div>
</div>

Also, as @Gabe already mentioned, ngInfiniteScroll might be a good addition for your use case: http://binarymuse.github.io/ngInfiniteScroll/

like image 150
Jürgen 'Kashban' Wahlmann Avatar answered Sep 21 '22 21:09

Jürgen 'Kashban' Wahlmann