Angular: scope variable vs function - angularjs

What is better in Angular - to bind to a variable or to a function. In particular:
Is there any performance penalty due to digest calls or additional watches that are created for a function?
Are there any recommendations for what scope functions should and shouldn't do?
Example for two options:
<!-- With function -->
<button ng-disabled="noDataFoo()">Add</button>
<!-- With variable -->
<button ng-disabled="noDataFlag">Add</button>
Backing controller:
app.controller('sample', function($scope, $http) {
$scope.noDataFlag = true;
$scope.noDataFoo = function () {
return !$scope.data;
};
$http('/api/getdata').success(function(data) {
$scope.data = data;
$scope.noDataFlag = false;
};
});

I'm not a javascript performance expert or anything, but my naive opinion would be that the variable would out perform the function by MAYBE a couple of nanoseconds because it's a 2 step process.
Also, the example above would be just as easily accomplished using:
<button ng-disabled="!data">Add</button>

I made some tests and counted how many times the function is called under different circumstances. It occurs, the function is called the number of times it has binding, sometimes twice the number and it seems to happen after each external activity, like page reload or button click or AJAX call.
In simple words, if you have <button ng-disabled="noDataFoo()"> and then {{noDataFoo()}} in HTML, the function will be called 4 times at page load, then another 2 or 4 times if some $http service brings data, and another 2 or 4 times if some other button was clicked. From experiments, the number is 2 if noDataFoo doesn't change and 4 if it does change. By the way, the same holds for clicks on another controller.
My conclusion is: it's OK to bind to quick functions. For longer ones it's better to keep number of bindings small. And for even longer ones it's wiser to cache the result and handle "manually" cache updates.

Related

Using ng-class with a function call - called multiple times

I'm using Ionic and want to dynamically change the background colour of each item in an <ion-list> based on the data. I thought I'd do this by way of a function call to return the correct class
<ion-list>
<ion-item ng-repeat="singleCase in allCases" ng-class="getBackgroundColour(singleCase)" class="item-avatar">
<h2>{{singleCase.date}}</h2>
<p>{{singleCase.caseType}}</p>
</ion-item>
</ion-list>
This is the controller at present
.controller('AllCasesCtrl', ['$scope', '$log', 'dummyData', function($scope, $log, dummyData) {
$scope.allCases = dummyData.cases;
$scope.getBackgroundColour = function(singleCase){
$log.log("getBackgroundColour called...singleCase type: " + singleCase.speciality);
var colourMap = {
speciality1: "speciality1Class",
speciality2: "speciality2Class",
speciality3: "speciality3Class"
};
return colourMap[singleCase.speciality];
};
}])
On checking the console, the getBackgroundColour() function is being called 7 times for each <ion-item> in the list. Why is this, and should I avoid using a function call in ng-class?
AngularJS works with dirty checking: it needs to call the function each cycle to be sure that it doesn't return a new value and that the DOM doesn't need to be updated.
It's part of the typical process of the framework, and calling a function as simple as yours shouldn't have any negative performance impact. Readability and testability of your code is far more important here, so keep the logic in your controller.
One simple things to do, however, is simply to move the declaration of colourMap, which is a constant, outside of your function:
var colourMap = {
speciality1: "speciality1Class",
speciality2: "speciality2Class",
speciality3: "speciality3Class",
};
$scope.getBackgroundColour = function(singleCase) {
return colourMap[singleCase.speciality];
};
Your way is fine as long as your list is not some huge size. That being said if you are using angular 1.3 and you want to lower the number of calls you can change your ng-class to ng-class="::getBackgroundColour(singleCase)". This applies one time binding so once the value is stable it will not check again. This would most likely mean two calls per item.

Need explanation on a angular direcive load

I just want to understand why in the following jsFiddle 'here is a lo' is printed three times.
http://jsfiddle.net/wg385a1h/5/
$scope.getLog = function () {
console.log('here is a log');
}
Can someone explain me why ? What should I change to have only one log "here is a log" (that's what I would like this fiddle do). Thanks a lot.
Angular uses digest cycles/iterations to determine when state has changed and needs to update the UI. If it finds any change on one of it's cycles, it keeps rerunning cycles until the data stabilizes itself. If it's done 10 cycles and the data is still changing, you'll see a rather know message: "angularjs 10 iterations reached. aborting".
Therefor, The fact that you are seeing the message displayed 3 times is because you have a simple interface. In fact, you can get up to many more such messages in the log, due to the fact that your directive uses {{getLog()}}. Angular keeps evaluating the expression to see if it changed.
To avoid such problems, under normal circumstances, you should store the value returned by the function you want called only once in the $scope object inside the controller and use that variable (not the function call) in the UI.
So in the controller you'd have $scope.log = getLog() [assuming it returns something, and not just writing to the console] and in the directive use the template {{log}}. This way, you'll get the value only once, per controller instance.
Hope I was clear enough.

Angular: Calling function that changes scope variable on interval

I've built a function that does some text transformations on a string. I'm calling this function from my view.
I would now like to call this function from a controller with a set interval,every 3 seconds.
I was trying with this in my controller:
$scope.MyTextFunction = function(input) {
cancelRefresh = $timeout(function myFunction(input) {
console.log(input);
// Do things here
cancelRefresh = $timeout(myFunction, 3000);
},3000);
};
I'm having some trouble getting this to work. Specifically, it seems all the functions that are called in the view are being triggered again. So console.log(input) gets called 4 times, then 8 times, then 16 times, etc, every 3 seconds. (I'm guessing this is due to the digest cycle? I'm not totally clear on this).
As you can expect, after staying on this view for 20 seconds the browser becomes unresponsive.
How can I make write a function that
triggers every 3 seconds,
is tied to the scope so I can use the output in the view and
doesn't trigger all the existing functions that already exist in the view?
I've set up a plunkr that contains the text transformation string: http://plnkr.co/edit/JlEBlCBG3roCqkiKegI6?p=preview

Angularjs reuse calculated values

I need to use a calculated value to hide elements and do other things in the template.
<button ng-hide="expensive()" ng-click="foo()">foo</button>
<button ng-show="expensive() && otherFunction()" ng-click="bar()">bar</button>
<span ng-show="expensive()">Bas</span>
This causes the excecution of expensive() multiple times for each $digest cycle.
I want to reuse its result, but it needs to be executed for each digest - just not multiple times per digest.
Is there any best practice for reuse function results that needs to be re-calculated each digest?
* update *
This function works with a huge object and its properties can be changed with a lot of input fields and sub-formulars on the page. It has multiple one to many relations. If I have to add events / ngChanges to each field and if I miss only one, this will not work correcly.
You didn't really give us enough information to give you the best option for your situation.
Very generally speaking, the best option is to have whatever interactions that cause the return value of expensive() to change update a $scope.property and just use that in your view.
In other words, don't use functions on scope in your view unless you're setting up a binding like ng-click or something. Instead, update properties on your scope whenever they need updating and just reference them directly.
Caution: This might tempt you to use $watch... don't do that. There are cheaper, more effective ways to trigger updates, like ngChange, or other such events.
You have a few options:
make expensive() a $q promise. Angular templates understand $q promises and resolve them accordingly
store the value when running the function, if the value exists return it first thing in the function. You can add a flush param to it in case you want to flush the cached value.
If the values truly never change once computed, you could put them in a resolve
If these values are the result of some DOM work, create a directive to do this and $emit actual changes to the parent scope.
Try this.
<button ng-hide="result" ng-click="foo()">foo</button>
<button ng-show="result && otherFunction()" ng-click="bar()">bar</button>
<span ng-show="expensive()">Bas</span>
Controller:
$scope.expensive = function() {
... do stuff ...
$scope.result = ...;
return $scope.result;
}

AngularJS: apply function after model loads

I print a dataset i obtain from a service in a list. That's ok.
So, i have two functions, *paint_other_avatars()* and *paint_more_participants()*, (they are http calls) in each item to get some realated data.
My problem is AngularJS won't render the list until all the data is fetched, so the page takes very much to load. I want to avoid that delay.
Initially, I planned enhance my SQL query to get all the needed data with a sole call, but now i think so many calls aren't so bad if i would do them asyncronously or render the list before this secondary calls.
I know one of my problems is setting the calls in ng-init(), but i don't know any directive like ng-after()
This is my code simplified:
<li ng-repeat="plan in plans | orderBy:get_timing" ng-animate=" 'animate' " ng-class="status(plan)">
<div class="desc_plan">
<span class="gris_24">{{plan.title}}</span>
</div>
<div class="asistentes">
<span id="other_avatars_{{plan.id}}" ng-init="paint_other_avatars(plan)"></span>
<span id="more_participants_{{plan.id}}" ng-init="paint_more_participants(plan)" class="asistentes_mas"></span>
</div>
</li>
EDIT for j_walker_dev:
hmmmm I am trying your solution but i have found a problem
i have
$scope.plans = Plan.query({token: token});
i guess this type of calls are asynchronous, so if i put
angular.forEach($scope.plans, function(plan) {
$scope.paint_other_avatars(plan);
$scope.paint_more_participants(plan);
});
the program is not entering in the forEach because it has not time to do it. maybe so?
For each iteration in the ng-repeat you are making two separate http calls? i think your problem lies there. Usually making any kind of web calls in a loop is a bad practice idea. Huge performance hit.
I suggest first to figure out a better design pattern to get the data without making separate calls for each. But if you must you could separate the requests from the view layer into your javascript controller.
What i mean is make your initial call to get plans. And then do a for loop over them in javascript and call paint_other_avatars and paint_more_participants in that loop. This way the async calls have no relation to the template rendering and does not slow down, once the plans load, your html will render. While in the background you are making your other calls for paint_other_avatars and paint_more_participants.
$scope.$watch('plans', function(newValue, oldValue) {
if (newValue.length) {
_.each(plans, function(plan) {
paint_other_avatars(plan);
paint_more_participants(plan);
})
}
})
I dont know what your two function calls are doing, but will this work in making your template load faster?
To
well, fortunately seems query() function accepts callback.
so i solved it this way
Plan.query({token: $cookies.ouap_token}, function(result){
$scope.plans = result;
angular.forEach($scope.plans, function(plan) {
$scope.paint_other_avatars(plan);
$scope.paint_more_participants(plan);
});
});
on the other hand, I am not sure this way is faster :/

Resources