0

I have a products variable, which holds a nested array of hierarchical products, such as:

Product
     - cat_id: 1
     - name: Some Product
     - Children
          - Child Product 1
          - Child Product 2
                  - Children
                      - I can also have more children here
                      - And another
          - Child 3
Product 2
     - cat_id: 2
     - name: Some other Product
     - Children
         - A child product
         - another 

I also have another variable which is an array of products that have been purchased.

What I want to do is to display the full product list as above, but if the user has purchased it, to apply a class.

Here's where I'm at so far:

<ul>
    <li ng-repeat="product in all_products track by product.cat_id">
        {{ product.cat_name }}
            <ul ng-if="product.children.length>0">
                <li ng-repeat="l1_child_product in product.children track by l1_child_product.cat_id">

                    {{ l1_child_product.cat_name }}
                    <ul ng-if="l1_child_product.children.length>0">
                        <li ng-repeat="l2_child_product in l1_child_product.children track by l2_child_product.cat_id">
                            {{ l2_child_product.cat_name }}
                        </li>
                    </ul>
                </li>
            </ul>
    </li>
</ul>
</div>

What I want to do is for each

  • is to apply the class, if the contents of the second array, contains the current product's cat_id, for instance:
    <li ng-repeat="product in all_products track by product.cat_id" ng-class="foreach(otherarray as owned){ if(owned.cat_id==product.cat_id){ 'some_class' } }">
    

    I'm still very new to Angular so i'd like to know the proper way of achieving this.

    I'm currently porting my application from being purely server side with a static front end, to Angular. I was able to perform this sort of logic extremely quickly using a few nested for-loops with conditional statements in my old app.

    However, with Angular, this seems to cause the application to grind down to a very slow pace.

    I've heard of dirty-checking, which Angular uses and I think I'm hitting the bottlenecks that occur as a result as my datasets are generally fairly large (around 250 products, with around 40 purchases), but with up to 20 users being shown on a page. What's a good way of avoiding these performance issues when using Angular?

    Updated

    Here's the code I'm using at the moment:

    <div class="row" ng-repeat="user in ::users">
    
    <script type="text/ng-template" id="product_template.html">
        {{ ::product.cat_name }}
        <ul ng-if="product.children">
            <li ng-repeat="product in ::product.children track by product.cat_id"
                ng-include="'product_template.html'"></li>
        </ul>
    </script>
    
    <ul>
        <li ng-repeat="product in ::all_products track by product.cat_id"
            ng-include="'product_template.html'"></li>
    </ul>
    
    
    <table>
        <tr ng-repeat="licence in ::user.licences">
            <td>{{::licence.product.cat_name}}</p></td>
            <td>{{::licence.description}}</td>
            <td>{{::licence.start_date}}</td>
            <td>{{::licence.end_date}}</td>
            <td>{{::licence.active}}</td>
        </tr>
    </table>
    </div>
    

    This gives the desired output of:

    • Iterate over the users
    • Iterate over ALL of the products available
    • Display a list of all of their purchases (licences)

    However, it's incredibly slow. I just timed the page load, and it took 32 seconds to parse all of the data and display it (the data was available after around 100ms).

    I'm using the new :: syntax to prevent lots of two-way bindings but this doesn't seem to improve the speed at all.

    Any ideas?

  • Amo
    • 2,884
    • 5
    • 24
    • 46
    • 1
      Key rule : Avoid complex expressions in your bindings. In your case, adding a flag `purchased` in the object could help a lot – Michel Mar 04 '15 at 19:27
    • That's not possible, due to the amount of data overhead that'd involve. I currently pull in one global product list, and need to compare that against purchases. Otherwise, each user would need to return a full product list which would increase the amount of data transferred considerably, instead of just the products they've purchased. – Amo Mar 04 '15 at 19:29

    3 Answers3

    1

    Your question is 2 parts:

    • How do I display products and their children recursively?
    • In an efficient way, how do I add a class if a product has been purchased?

    Displaying Products and their Children

    This has already answered well by a previous question on Rending a Tree View with Angular.

    Efficiently adding a Purchased class

    The inefficiency you currently have is caused from looking through otherarray for every single product.

    There are various solutions on how to improve upon this but I think the easiest change for you would to make would be to use an {} instead of an array to track purchased products.

    { cat_id: true }

    For more information on why using an Object or Hash is faster looking at this question on Finding Matches between Arrays.

    Combined Solution

    Displaying Products and their Children

    <script type="text/ng-template" id="product_template.html">
      {{ product.cat_name }}
      <ul ng-if="product.children">
        <li ng-repeat="product in product.children"
        ng-include="'product_template.html'"
        ng-class="{ purchased : product.purchased }"></li>
      </ul>
    </script>
    
    <ul ng-app="app" ng-controller="ProductCtrl">
      <li ng-repeat="product in all_products"
      ng-include="'product_template.html'"
      ng-class="{ purchased : purchasedProducts[product.cat_id] }"></li>
    </ul>
    

    Effiecntly adding a Purchased class aka. otherarray -> purchasedProducts object

    I don't know exactly where otherarray is being constructed but a simple conversion would go as follows:

    var purchasedProducts = {};
    for (var i = 0; i < otherarray.length; i++) {
      var cat_id = otherarray[i];
      purchasedProducts[cat_id] = true;
    }
    
    Community
    • 1
    • 1
    D3RPZ1LLA
    • 281
    • 3
    • 8
    • Thanks for the recursive template tip! I've implemented what you've suggested (see my edit above) but it's still incredibly slow. It takes 32 seconds, to iterate over 10 users, each with around 10 purchases each, and loop over the 100 or so products for each user. That's around 1100 iterations in total, but the time taken seems out of proportion. – Amo Mar 05 '15 at 11:04
    • This solution shouldn't be taking that long. I'd estimate that iterating over 10 users to take under 1 second easily. The time complexity of these solution is only O(n). Try trouble shooting for the cause of the slow performance: remove the code above and see if the page still takes 32 seconds to load. If it is still slow check to see if you have any additional function calls within the loop that might be slowing down performance. – D3RPZ1LLA Mar 10 '15 at 22:54
    • It's definitely the code. Granted, each user within the array has a lot of data and likewise for the all_products array. In the end, I had to do the work in pure JavaScript by iterating over the data and assigning a new attribute containing the html to each user. – Amo Mar 11 '15 at 09:31
    0

    Remember that ng-class can be a function call.

    jlowcs
    • 1,933
    • 1
    • 10
    • 16
    0

    Starting with Angular 1.3, there is native Bind Once support. when iterating over a large number of items, you can eliminate the watchers on static elements which will not change.

    For Example:

    <li ng-repeat="product in ::all_products track by product.cat_id">
    

    Will iterate through the all_products array once to populate the ngRepeat, but will not continue to be bound to $watch for change tracking. Smart use of :: can drastically improve performance.

    Also, converting ng-class= to a function instead of an inline expression evaluation can improve performance and give you greater control over your output.

    Claies
    • 22,124
    • 4
    • 53
    • 77
    • If a single page has multiple occasions where the all_products array is iterated over, am I right in thinking that each time it's iterated over, it won't set up different watchers? Can you please edit your answer to give a bit more info on :: and the ng-class function? – Amo Mar 04 '15 at 20:29