【问题标题】:Reducing event triggers on evaluated ng-disabled in AngularJS减少 AngularJS 中评估的 ng-disabled 的事件触发器
【发布时间】:2014-06-17 03:26:18
【问题描述】:

用例

我在一个页面上有一组控件,大约 60 多个。每个控件都会评估一个字符串表达式,以根据在表单本身中输入的数据来决定是否禁用该控件。表达式是由最终用户创建的,所以它可以是任何东西。

我遇到的问题是表单和评估相当复杂,并且会减慢 UI。最终代码将包含需要在作用域对象中查找数据的表达式,并将包含循环等...

演示

这是一个jsfiddle 来模拟我正在尝试改进的内容。它不是最终代码,因为该模块相当大。但是,这应该可以说明我正在尝试实现(或减少)什么。这个演示速度很快,因为表达式相当简单,但我希望能够减少调用函数is 的次数。

[edit] 我已经更新了演示,以尽可能多地包含最终生产代码的核心逻辑。这与将要完成的目标一样接近。

模板

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

控制器

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

问题

如何提高这些评估的性能?

解决方案?

我注意到评估是在 keyUp 事件上执行的。我怀疑我不能使用本机 ng-disabled 并且必须创建一个触发模糊事件的指令,但我想看看还有什么其他想法可用。

【问题讨论】:

  • 使用 AngularJs 的最新 1.3 版本,您可以为 ng-model-options 设置 debounce 值,这将延迟模型更新(进而调用 ng-disabled)直到设定的时间段之后。 See this answer
  • @JoseM 在另一个 Fiddle 中的建议:jsfiddle.net/marcolepsy/rXuHd/1 这要简单得多,但我将原始答案留作另一种方法参考

标签: javascript angularjs


【解决方案1】:

您可以继续使用 ngDisabled,方法是让 ngDisabled 评估一个仅在指令的链接函数规定的特定条件下才更改的简单值,而不是每次 Angular 运行摘要循环时为每个小部件调用一个函数。

这是modified Fiddle。为此,您需要:

添加一个属性限制指令,它用以下行为装饰元素:

  • 在父控制器中注册一个观察者
  • 在元素上绑定侦听器,当触发该元素时会调用一个检查所有表达式的函数

...

directive('widgetWatcher', function($timeout){
    return {
        restrict: 'A',
        require: 'ngModel',
        scope: true,
        link: function(scope, elm, attr) {          

            // register observer for this directive by passing its scope
            scope.addExpressionObserver(scope);

            // Check expressions at most once every 1s or immediately when input is blurred
            var debounce;
            elm.bind('input', function() {
                $timeout.cancel(debounce);
                debounce = $timeout( function() {
                    scope.$apply(function() {
                        scope.checkExpressions();
                    });
                }, 1000);
            });
            elm.bind('blur', function() {
                scope.$apply(function() {
                    scope.checkExpressions();
                });
            });

        }
    }
})

向父控制器添加一个数组:

  • 向数组添加观察者的函数(每个指令的范围)
  • 另一个迭代集合,评估每个表达式并为每个小部件设置 isDisabled 范围变量...这由发生输入或模糊的指令调用

...

$scope.expressionObservers = [];

$scope.addExpressionObservers = function(dScope) {
    $scope.expressionObservers.push(dScope);
};

$scope.checkExpressions = function() {
    angular.forEach($scope.expressionWatchers, function(val){
        val.isDisabled = $scope.is(val.widget.expression);
    });
};

不知道这是否能解决您的所有问题,或者是否没有更好的方法在 Angular 中执行此类操作,但希望它至少为您提供了另一种考虑的方法(这是您所要求的) .

【讨论】:

  • 去抖动解决方案看起来不错,我可能会实现它或运行不稳定版本的 Angular。感谢您抽出宝贵时间。
  • 我的荣幸。我回答 SO 主要是为了学习新事物,我在这里这样做了。如果您在 ng-model 值上有其他观察者不会从限制中受益,那么我可以想象限制 .$setViewValue() 调用的唯一潜在缺点。不过我敢打赌,这对你来说不是问题。
猜你喜欢
  • 2014-06-28
  • 2014-11-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-10-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多