1

Can anyone confirm or deny the below scenario, and explain your reasoning? I contend that this would cause two UI renders and is therefore less performant.

Suppose in Angular you have a data model that is hooked up to a dropdown in the UI. You start with a data model that is an array of objects, you clear the array, you re-fill the array with exactly equivalent objects that are different only in that a property has been changed:

  [obj1, obj2, obj3, obj4]
  // clear the array
  [] // the first UI render event occurs
  // you fill the array with new objects that are the same except the value
  // of one property has changed from true to false
  [obj1, obj2, obj3, obj4] // a second UI render event occurs

I contend that this is more performant:

  [obj1, obj2, obj3, obj4]
  // change a property on each object from true to false
  [obj1, obj2, obj3, obj4] // a single render event occurs

Thank you for looking at this.

4
  • why do you need to manually clear the array, instead of just repopulating it with the new values? Commented Jun 17, 2016 at 17:35
  • i think your second example would re-render for each change, so once for each object Commented Jun 17, 2016 at 17:35
  • 1
    Why not just profile both techniques? I'm guessing that both are, depending on the nature of the objects and changes to the properties, so fast that it matters little. Commented Jun 17, 2016 at 17:37
  • 1
    Too many unknowns. Do you clear the array and repopulate in the same function, or what is happening in between? Commented Jun 17, 2016 at 17:45

2 Answers 2

1

If the steps in your first example are supposed to be run synchronously, the assumption is false. Since JavaScript is single-threaded, angular won't have a chance to even notice that you have emptied the array before re-filling it. For example:

 // $scope.model === [obj1, obj2, obj3, obj4];
 $scope.model.length = 0; // clear the array
 // $scope.model === [] but no UI render occurs here
 $scope.model = [obj5, obj6, obj7, obj8]; //re-fill with new objects
 //UI render will happen later, and angular will only see the change
 //from [obj1, obj2, obj3, obj4] to [obj5, obj6, obj7, obj8]

If the changes are supposed to involve asynchronicity, the delay in these asynchronous operations is likely to take much more time than the empty array render in between, so I wouldn't be concerned about that either.

The possible performance differences come from other things, like from creating new objects or angular needing to do deep equality checks when references haven't changed.

I doubt that this would be the bottleneck of any angular app, though, so I suggest you go with whatever suits your code style better. (Especially as mutable vs immutable objects is quite an important design decision to make).

Sign up to request clarification or add additional context in comments.

Comments

0

Assuming that the process is in steps which require user interaction, the steps will be as follows. Note that the numbers in the indented lists represent the high level process that Angular uses.

  1. Angular renders view with default array [obj1, obj2, ob3]
    1. A watcher is created which watches array reference
    2. Angular sets up watchers on the total array of objects
    3. Watcher also watches properties of objects within the array
  2. User interacts with view, causing array to be set to []
    1. Watcher #1 above fires on new array reference, builds watchers for any new objects and deconstructs watchers for old references.
  3. User interacts again to build new array of items with one property change [obj1, obj2′, obj3].
    1. Watcher #1 fires, noticing a new array reference
    2. Angular builds watchers for each object and properties of those objects within the array

In terms of speed, step #2 is essentially a NoOP. What you're probably running into is the time it takes Angular to construct and deconstruct the watchers when a new array of objects is created.

When angular sets up a watcher on an object or an array of objects it'll add in a $hash property to all objects within the array. Now imagine that a new array is created which looks the same as the old one but all of the object references in memory are new and the $hashes are gone or changed. This causes Angular to fire all of the $watch statements for that scope variable.

You shouldn't be creating a new array or assigning the original scope array to a new value. This is a bad pattern. The code below shows two methods: one which will cause the watchers to fire more than desirable, and a better method which will only fire the watchers that need to be fired.

scope.myArr = [{n:1, a:true}, {n:2, a:true}, {n:3, a:true}];

//BAD PATTERN
scope.doSomething = function(n){
    scope.myArr = [];

    //somehow it gets a new array
    getNewArrayFromAPI(n)
    .then(function(response){
        scope.myArr = response.data;
    });
    //[{n:1, a:true}, {n:2, a:false}, {n:3, a:true}]
}  

//user interaction
scope.doSomething(2);

The following good pattern updates the array in place, never changing the references to the original objects unless it needs to add a new object to the array.

//GOOD PATTERN
scope.myArr = [{n:1, a:true}, {n:2, a:true}, {n:3, a:true}];  

scope.doSomething = function(n){
    //this method shows an in-place non-HTTP change
    scope.myArr.forEach(function(curr){
        if(curr.n === n){
            curr.a = false;
        }
    });

    scope.getChangedArray(n);
};

//represents an HTTP call, POST or PUT maybe that updates the array
scope.getChangedArray = function(n){
    $http.post("/api/changeArray", n)
    .then(function(response){
        //response.data represents the original array with n:2 changed
        response.data.forEach(function(curr){
            var match = scope.myArr.filter(function(currMyArr){
                return currMyArr.n === curr.n;
            });
            if(match.length){
                //update the existing properties on the matched object
                angular.extend(match[0], curr);
            }else{
                //new object
                scope.myArr.push(curr);
            }
        });
    })
};

//user interaction
scope.doSomething(2);

1 Comment

Allan, you're the greatest. Thank you so much.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.