Use Case
I have a set of controls on a page, roughly 60+. Each control evaluates a string expression to decide whether or not the control is disabled based on data entered within the form itself. The expression is created by the end-user, so it could be anything.
The problem I have is that forms and evaluations are fairly complex and are slowing down the UI. The final code will have expressions that require look ups against data in scoped objects and will contain loops etc...
Demonstration
This is a jsfiddle to simulate what I am trying to improve. It isn't the final code as that module is fairly large. However, this should give a picture of what I'm trying to achieve (or reduce). This demonstration is fast because the expression is fairly simple, but I'd like to be able to reduce the number of times the function is
is called.
[edit] I've updated the demonstration to include as much of the core logic as I can for the final production code. This is as close as it gets to what will be accomplished.
Template
<div ng-app ng-controller="ctrl">
<div ng-repeat="widget in widgets">
<label>Widget #{{$index}}
<input type="text" ng-model="widget.value"
ng-disabled="is(widget.expression)" />
</label>
</div>
</div>
Controller
var ctrl = function ($scope) {
$scope.widgets = [];
/** Core Logic Below Here **/
var getValueByName = function (name) {
for (i = 0, k = $scope.widgets.length; i < k; i++) {
if ($scope.widgets[i].name == name) {
return $scope.widgets[i].value;
}
}
return 0;
};
var replaceNameWithValue = function () {
var result = this.replace(/( |^)(widget_\d+?)( |$)/g, function ($0, $1, $2, $3) {
return parseInt(getValueByName($2), 10);
});
return result;
};
$scope.is = function (expression) {
console.log('firing');
try {
var exp = replaceNameWithValue.call(expression);
return $scope.$eval(exp);
} catch (e) {}
return false;
};
/** Logic to generate random test cases **/
var numWidgets = 60;
var operators = ['+', '-', '*'];
var comparators = ['===', '!==', '>', '<', '>=', '<='];
var getRandomInt = function (max) {
return Math.floor(Math.random() * max);
};
var makeExpression = function () {
var a = getRandomInt(numWidgets);
var b = getRandomInt(numWidgets);
var c = getRandomInt(20);
var op = operators[getRandomInt(operators.length)];
var cf = comparators[getRandomInt(comparators.length)];
return {
expression: ['widget_' + a, op, 'widget_' + b, cf, c].join(' ')
};
};
var makeRandomObject = function (index) {
var obj = makeExpression();
obj.name = "widget_" + index;
obj.value = 0;
return obj;
};
var preLoad = function (numWidgets) {
for (i = 0; i < numWidgets; i++) {
$scope.widgets.push(makeRandomObject(i));
}
};
preLoad(numWidgets);
};
Question
How can I improve the performance for these evaluation?
Solution?
I noticed that the evaluations are executed on the keyUp event. I suspect I can't use the native ng-disabled
and will have to create a directive that fires on the blur event, but I'd like to see what other ideas are available.