AngularJS过滤器导致无限$ digest循环异常

时间:2015-09-14 22:43:54

标签: angularjs filter infinite-loop

我想在这里描述的问题非常复杂,我知道我的英语远非完美,所以如果有人有足够的耐心阅读这篇文章,我将非常感激。

Ad rem:我有一个自定义指令(称为元数据),其中我使用“angularjs-dropdown-multiselect”模块:http://dotansimha.github.io/angularjs-dropdown-multiselect/来提供多选字段。它工作正常,但问题是:指令被多次调用(因为我需要这种类型的几个字段),其中一些字段是相互依赖的。例如,我有两个字段:“居住地”和“人物”。当用户在多选字段中选择一个或多个居住地点时,应该过滤掉一些“人物”字段选项以仅留下居住在所选地点的人。我无法做到正确:无论我尝试什么,过滤过程都会触发“无限$ digest循环”异常。

每个字段的可能选项列表都是从json数据文件(称为meta_data.json)中提取的,该文件具有以下格式:

[{"person": "JeJu1929", "living-place": "Zabrid'"}, 
 {"person": "VM1996", "living-place": "Velykyj Bereznyj"},
 {"person": "VB1957", "living-place": "Zabrid'"}]

因此,当用户选择'Zabrid'时,'Person'字段的选项列表应该包含两个条目:'JeJu1929'和'VB1957'。 用户所做的选择存储在单独的工厂函数(称为 queryKeeper )以及上述文件中的数据中。

整个过程的架构如下:

  1. 工厂函数读取包含数据的json文件( meta_data.json )并将其存储在对象列表中( metaValues
  2. 在指令控制器(例如,为“居住地”字段调用)中,读取数据对象( metaField )并获取所有可能选项的列表
  3. angularjs-dropdown-multiselect要求选项列表包含格式为{id: value, label: option-name}的对象,因此在获取选项列表的过程中,每个选项都从字符串类型转换为所需的对象格式,所以 - 对于'生活场所'字段 - 我最终得到的选项列表如下:

    [{id:'Zabrid',label:'Zabrid'},{id:'Velykyj Bereznyj',label:'Velykyj Bereznyj'}]

  4. 当用户在字段中选择一些选项时,此信息将转到工厂函数(并存储在变量 metaFields 中)

  5. 假设我们要根据用户在“生活场所”字段中所做的选择过滤掉“人物”字段选项。过滤器函数可以选择(来自 metaFields 变量),数据对象( metaValues )以及要缩小的选项列表(在我们的示例中包含三个选项) :“JeJu1929”,“VM1996”,“VB1957”)。如果用户在“居住地”字段中选择了“Zabrid”选项,则过滤器功能会抛出“VM1996”,因为其居住地不是“Zabrid”。

  6. 到目前为止,一切正常。但是当过滤器返回新的选项列表(它是旧列表的子集)时,$ digest循环会一次又一次地触发,最后抛出“无限$ digest循环”异常。我认为问题在于对象的比较(即使对象实际上是相同的,角度仍然认为它们是不同的),但事实并非如此:当为“人物”字段选项调用过滤器函数时,它作为参数,比方说,四个选项,并返回,比方说,两个。然后调用过滤器用于其他字段(如“居住地点”等),再次为“人物”调用 - 使用相同的参数(即四个选项,而不是两个),就像第一次一样。这令人困惑,我被困住了。

    代码的相关部分:

    档案:metadata.html(模板)

    <div>
            <div ng-if="meta.type == 'select'" ng-dropdown-multiselect="" options="values|matchValues:meta.name" selected-model="model" extra-settings="multiSettings" translation-texts="hint" checkboxes="true">
    </div>
    

    文件:metadata.js(我的自定义指令)

    corpus.directive('metadata', ['queryKeeper', 'gettextCatalog', function(queryKeeper, gettextcatalog) {
        return {
            templateUrl: 'jsapp/languageQuery/metadata/metadata.html',
            restrict: 'E',
            scope: {
                index: '=',
                place: '='
            },
            controller: function($scope, gettextCatalog) {
    
                // function obtains the list of all possible options for the field
                getValues = function () {
                    values = [];
                    metadata = queryKeeper.getMetaValues ();
                    name = $scope.meta.name.slice (5);  
                    for (i = 0; i < metadata.length; ++i)
                        if (typeof metadata[i][name] != 'unknown')
                        {
                            var tmp = angular.copy (metadata)[i];
                            tmp['name'] = name;
                            tmp['id'] = metadata[i][name];
                            tmp['label'] = tmp[name];
                            values.push (tmp);                      
                        }
                    values = values.slice().sort(function(a,b){return a[name] > b[name]}).reduce(function(a,b){if (a.length == 0 || a.slice(-1)[0][name] !== b[name]) a.push(b);return a;},[]);
                    return values;
                }
    
                $scope.meta = queryKeeper.getMeta($scope.index, $scope.place); // provides access to the field data, like field name, choices made etc.
                $scope.values = getValues ();   // actual options list (to be filtered)
                $scope.model = [];  // stores the user choices
    
                $scope.multiSettings = {showCheckAll: false, buttonClasses: 'btn btn-default meta', dynamicTitle: false, scrollable: true, scrollableHeight: '150px'}; // angularjs-dropdown-multiselect settings
                $scope.hint = {buttonDefaultText: $scope.meta.hint};
    
                // watch function that sends the user choices to the factory function
    
                $scope.$watchCollection ('model', function (newValue) {
                    queryKeeper.setMetaMulti ($scope.index, $scope.place, newValue);
                });         
            }
        };
    }]);
    

    文件:corpus.js(主模块)

    corpus.factory('queryKeeper', ['loadLanguages', 'loadMetaData', function(loadLanguages, loadMetaData) {
    
        var metaFields = [];
        var metaValues = [];
    
        loadMetaData.getMetaFile('settings/meta_data.json').then(function(response) {
            metaValues = angular.fromJson(response);}); 
    
        return {
    
            getMeta: function(i, place) {
                return metaFields[i * 3 + place];
            },
            setMetaMulti: function (i, place, item) {
                metaFields[i * 3 + place].multiValue = [];
                for (j = 0; j < item.length; ++j)
                {
                    metaFields[i * 3 + place].multiValue.push (item[j].id);
                }
                return item;
            },
    
        }
    }]);
    
        corpus.filter ('matchValues', ['queryKeeper', function (queryKeeper) {
        return function (values, name) {
            metaSet = queryKeeper.getMetaAll ();
            var tag = '';
            if (name == 'meta_living-place')
                tag = 'variety';
            else if (name == 'meta_person')
                tag = 'living-place';
            else
                return values;
            console.log ('init:', name, values);
            metadata = queryKeeper.getMetaValues ();
            newValues = [];
            var found = [];
            for (i = 0; i < metaSet.length; ++i) {
                    if (metaSet[i].name == 'meta_' + tag) {
                        found = metaSet[i].multiValue;
                        break;
                    }
                }
            if (typeof found == 'undefined' || found.length == 0)
                return values;
            for (j = 0; j < values.length; ++j) {
                for (i = 0; i < found.length; ++i)
                    if (found[i] == values[j][tag])
                        newValues.push (values[j]);                 
            }
            console.log ('new:', name, newValues);  
            return newValues;
        }
    }]);
    

    更新

    我花了一段时间,但我创建了JSFiddle:https://jsfiddle.net/HB7LU/17604/。令我惊讶的是,在我的代码中是另一个我不知道的问题:在我的模块中,我使用了'ngAnimate'模块,它阻止了选择列表的过滤。当我删除此依赖项时,列表开始正常运行。我很好奇为什么。仍然存在无限$ digest循环问题。

0 个答案:

没有答案