Amo
Amo

Reputation: 2944

Angular conditionally apply class if array contains value

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?

    Upvotes: 0

    Views: 2204

  • Answers (3)

    D3RPZ1LLA
    D3RPZ1LLA

    Reputation: 281

    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;
    }
    

    Upvotes: 1

    Claies
    Claies

    Reputation: 22323

    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.

    Upvotes: 0

    jlowcs
    jlowcs

    Reputation: 1933

    Remember that ng-class can be a function call.

    Upvotes: 0

    Related Questions