1

I have a user object and I have a complex logic, that I want to unit test, which takes a user object and decides how it should be displayed - which css class should be used.

There are two approach that I consider:

<td class="{{ user | classify }}">

or

<td class="{{ user.cssClass }}"><!-- or --><td ng-class="user.cssClass">

or

<td ng-class="computeCssClass(user)">

The first approach assumes I create a filter that based on the provided user objects returns the css class.

The second approach assumes I add a new attribute cssClass to the model and whenever a new user object is created (fetched from the REST API) I compute the cssClass attribute.

The third approach assumes I create a function which computes the css class for the provided user object.

What are the pros and cons of the above three approaches?


I have created a jsfiddle to play with these three approaches.

Joao Polo
  • 2,153
  • 1
  • 17
  • 26
Adam Siemion
  • 15,569
  • 7
  • 58
  • 92

2 Answers2

1

This could use ng-class it self with an expression.

<td ng-class="{ active: user.active, suspended: user.suspended }">

Or if you are passing the cssClass directly from the API then that would be more easier

<td ng-class="user.cssClass">

Edit

1st Approach


Pros: Will give you desired output :)

Cons

  1. In your 1st approach by creating filter you are going to return css class name from it. Theoretically it will work, but if you think technically filter is mostly used on the collection object to filter out data based on filter criteria.
  2. Also using attribute with the {{}} wouldn't make sense to evaluate a class value, as angular does provide ng-class which is dedicated to do it.

2nd Approach

It looks pretty good. but in this you need to refactor your code bit. By moving it to utilService & lets use that code for ng-class directive by calling method from controller.

HTML

<td ng-class="cssClassComputationCode()">

Service

app.service('utilService', function(){
    var self = this;
    //below method can be testable by injecting its dependency in testing module
    self.cssClassComputationCode = function(){
        var cssClass = '';
        //here the computation thing will happen 
        cssClass = 'active';
        //some more code
        return cssClass;
    };
});

Controller

app.controller('myCtrl', function($scope, utilService){
     //assigning service method reference to controller scope variable
     $scope.computeCssClass = utilService.cssClassComputationCode;

     //other code here

});
Pankaj Parkar
  • 134,766
  • 23
  • 234
  • 299
  • I do not want to use expressions with ng-class because: 1) they will be duplicated in many places in the code, I want the logic that decides how to display a user in once place, 2) the actual logic I have is more complex than just checking the `active` and `suspended` attributes. – Adam Siemion Jan 16 '16 at 09:59
  • thanks for the suggestion, I have added this as another possibility. It seems very similar to the filter solution. Can you compare this approach with the second approach? In terms of its impact on the two-way data binding e.g. – Adam Siemion Jan 16 '16 at 10:19
  • @AdamSiemion still you need any explanation why you shouldn't go for 1st approach? – Pankaj Parkar Jan 16 '16 at 11:01
  • yes, please, can you compare the performance implications of the filter or function vs computed attribute solution? – Adam Siemion Jan 16 '16 at 11:08
1

The only significant difference I can think of is around the data-binding

1st approach, using filter

<td class="{{ user | classify }}">

Pro: leverage angular's powerful filter mechanism, shorter and nicer syntax

Con: a $watch will be put on the user object to watch for changes, and then call the filter, which actually loop through $filterProvider to find the correct provider function and execute it (I guess you'll figure out the overhead implication of this process)

2nd approach, using object's property

<td class="{{ user.cssClass }}"><!-- or --><td ng-class="user.cssClass">

Pro: just a normal 2-way binding (the magic of Angular JS), and you are doing it in the Angular-way

Con: not really a "con", this approach leverage automatic 2-way binding, which is a $watch on the user object's property to watch for changes, and update the class attribute

3rd approach

<td ng-class="computeCssClass(user)">

Pro: You have more control of when the class attribute got updated (by the return value of the function)

Con: it's easier to mess things up. [Edited] Overhead of $watch a function: the function is executed multiple times in a $digest and its return value got comparing to watch for changes, can be expensive.

If performance is what you care about most, the 3rd approach would be a good choice for you.

If you want to use ng-class, of all the above approaches, the 2nd approach will have the least overhead.

You can consider one-time binding, or a custom directive to handle updating class attributes. If you use the "class" attribute, you will be able to leverage the one-time binding syntax:

<td class="{{ ::user.cssClass }}">

Pre-compiling some presentation attributes from the data is always good, unless you have very specific requirement such as changing the user's dashboard background colour/image basing on the current weather of his/her location (well, even with this requirement, the weather is what should be $watch for)

I saw that you compute the class on a <td>. It's very easy to reach to the number of $watch that slows the page down significantly with table (read the 2nd answer of the referenced question above)

Anyway, it's all up to you which approach should you use, basing on the real-world context of the application you are developing.

Mr. Duc Nguyen
  • 1,049
  • 7
  • 16
  • Thanks Duc Nguyen for the answer, but isn't the 3rd approach using the same $watch on the user object as the 1fst approach? The 2nd solution puts $watch on `user.cssClass`, while 1st and 3rd on the complete `user` object, is that correct? – Adam Siemion Jan 20 '16 at 10:49
  • Thanks @AdamSiemion for your comment. You are correct about the watch. I have updated my answer. The watch in 3rd approach is a watch for a function, which has more overhead that watching for object property. – Mr. Duc Nguyen Jan 20 '16 at 23:20