【问题标题】:Multiple instances of custom directive, confusion with ngModel自定义指令的多个实例,与 ngModel 混淆
【发布时间】:2015-04-09 05:21:27
【问题描述】:

我创建了一个自定义指令来呈现问题的滑块(本质上是包装 jquery ui 滑块)。该指令接受一个 ngModel 并在用户使用滑块时更新它,并且有一个 $watch 附加到父模型(传递给指令的 ngModel 只是父模型的一部分)。该指令在一个页面上有多个实例。

我遇到了手表问题,因为手表似乎总是出现在页面上的最后一个问题上。因此,例如一个有 10 个问题的页面,使用问题 1 上的滑块 - 触发最后一个问题(问题 10)的手表。我认为该问题与指令/隔离范围和/或监视功能有关,但我无法解决。

this.app.directive('questionslider', () => {
    var onChangeEvent = (event, ui) => {
        updateModel(ui.value);
    };

    var onSlideEvent = (event, ui) => {
        updateUi(event, ui);
    };

    var updateUi = (event, ui) => {
        $(ui.handle).find(".ff-handle-glyph > div").css("top", (ui.value) * - 10);
    }

    var updateModel = (newValue) => {
        // find value in values list...
        angular.forEach(isolatedScope.model.PossibleValues, function(value) {
            if (parseInt(value.Name) === newValue) {
                isolatedScope.$apply(function() {
                    isolatedScope.model.Value = value;
                });
            }
        });
    };

    var isolatedScope: any;

    return {
        restrict: 'AE',
        replace: true,
        template: '<div></div>',
        scope: {
            model: '=ngModel',
        },
        link: function(scope, element, attrs, ctrl) {
            isolatedScope = scope;
            scope.$watch(ngModelCtrl, function() {
                // use provided defaultValue if model is empty
                var value = isolatedScope.model.Value === null ? isolatedScope.model.DefaultValue : isolatedScope.model.Value;

                element.slider({
                    min: 0,
                    max: isolatedScope.model.PossibleValues.length,
                    value: value.Name,
                    change: onChangeEvent,
                    slide: onSlideEvent
                });
            }
        }
    };
};

在控制器中添加手表的代码

this.$scope.questions.forEach(function(question) {
    this.$scope.$watch(
        function() { return question; },
        function(newVal, oldVal) { this.updateQuestion(newVal, oldVal) },
        true
    );
});

UpdateQuestion 函数(现在只是输出当前问题)

function updateQuestion(newVal, oldVal) {
    // prevent event on initial load
    if (newVal === oldVal) {
        return;
    }

    console.log(newVal);
}

ng-repeat 标记实例化问题滑块

<div data-ng-repeat="question in Questions">
    <h4>{{question.QuestionText}}</h4>
    <p>{{question.RangeMinText}}</p>
    <questionslider ng-model="question"></questionslider>        
    <p>{{question.RangeMaxText}}</p>
</div>

问题 JSON 看起来像这样

{
    "DefaultValue": {
        "Id": "5",
        "Name": "5"
    },
    "Id": "1",
    "IsAnswered": false,
    "PossibleValues": [
        {
            "Id": "1",
            "Name": "1"
        },
        {
            "Id": "2",
            "Name": "2"
        },
        {
            "Id": "3",
            "Name": "3"
        },
        {
            "Id": "4",
            "Name": "4"
        },
        {
            "Id": "5",
            "Name": "5"
        },
        {
            "Id": "6",
            "Name": "6"
        },
        {
            "Id": "7",
            "Name": "7"
        },
        {
            "Id": "8",
            "Name": "8"
        },
        {
            "Id": "9",
            "Name": "9"
        },
        {
            "Id": "10",
            "Name": "10"
        }
    ],
    "QuestionText": "hows it haning?",
    "RangeMaxText": "not good",
    "RangeMinText": "Very good",
    "Type": 0,
    "Value": null
}
],
"Title": "Question title",
"Type": 0
}

所以问题是,无论我使用滑块指令更新哪个问题,它始终是传递给 updateQuestion 的最后一个页面。

更新 我尝试使用 $watchCollection,但似乎没有触发该事件。

this.$scope.$watchCollection(
    'questions',
    function (newVal, oldVal) {
        // prevent event on initial load
        if (newVal === oldVal) {
            return;
        }

        for (var i = 0; i < newVal.length; i++) {
            if (newVal[i] != oldVal[i]) {
                this.$log.info("update : " + newVal.Id);
            }
        }
    }
);

我也试过

function() { return questions; }

作为第一个表达式。还是没有运气。

也许对每个问题使用单独的控制器是我唯一的选择,但这似乎是一种解决方法。

更新 所以我尝试为每个问题使用单独的控制器,在控制器中为每个问题添加一个手表,奇怪的是,即使这样也重现了相同的场景。它仍然是页面上传递给 watch 函数的最后一个问题。

标记

<div data-ng-repeat="question in Questions" data-ng-controller="QuestionInstanceController">
    <h4>{{question.QuestionText}}</h4>
    <p>{{question.RangeMinText}}</p>
    <questionslider ng-model="question"></questionslider>        
    <p>{{question.RangeMaxText}}</p>
</div>

控制器代码

app.controller('QuestionInstanceController', function($scope) {
        console.log($scope.question); // outputs correct question here!
        $scope.$watch(
            function() { return $scope.question; },
            function(newValue, oldValue) { 
                console.log(newValue); // always outputs last question on page
            },
            true);
    }
}

这一定与我的自定义指令有关,当页面上有多个实例时,我是否会以某种方式覆盖以前的实例?

【问题讨论】:

  • 你能从 HTML 视图中放一些示例吗?
  • 我还建议在指令的隔离范围“ngModel”中命名一个值的形式很差,而且令人困惑。
  • 嗨迈克尔,我添加了标记。我认为 ngModel 是这种类型的最佳实践,因为它正在更新,但这并不是因为它是一个孤立的范围,对吗?
  • 我说的是在您的指令中将“范围:{ngModel:'='}”命名为 ngModel。还有“scope.ngModel.Value === null”。您正在使用 ngModel 作为范围变量的名称。不过,您应该在视图中使用 ng-model 指令。我会尽力为你解决这个问题。
  • TypeScript 不够清晰,我无法理解。我不知道你在 $watch 里做什么。我真的无法提供进一步的帮助,因为这不在 Javascript 中,而且我怀疑你的代码中我无法识别的东西是罪魁祸首。对不起。

标签: angularjs angularjs-directive angular-ngmodel


【解决方案1】:

所以我自己设法找到了解决方案。问题是我有一个“var isolatedScope;”在每次运行链接()时分配的指令中。我认为指令中声明的变量在每个实例上都是隔离的,但实际上它们在指令的每个实现上都会被覆盖。

因此解决方案是将 onChange 的实现直接移动到滑块组件的 init 中,从而避免以后访问范围变量的需要

this.app.directive('questionslider', () => {
    var onChangeEvent = (event, ui) => {

    };

    var onSlideEvent = (event, ui) => {
        updateUi(ui.value, ui.handle);
    };

    var updateUi = (value, element) => {

    }

    var onStartSlide = (event, ui) => {

    }

    var onEndSlide = (event, ui) => {

    }

    return {
        restrict: 'AE',
        require: 'ngModel',
        replace: true,
        templateUrl: 'templates/questionSlider.html',
        scope: {
            model: '=ngModel'
        },
        link: (scope: any, element, attrs, ngModelCtrl: ng.INgModelController) => {
            scope.$watch(ngModelCtrl, () => {
                // use provided defaultValue if model is empty
                var value = scope.model.Value === null ? scope.model.DefaultValue : scope.model.Value;
                var hasNoValue = scope.model.Value === null;

                if (hasNoValue) {
                    element.find('.ui-slider-handle').addClass('dg-notset');
                }

                element.slider({
                    min: parseInt(scope.model.PossibleValues[0].Name),
                    max: scope.model.PossibleValues.length,
                    value: parseInt(value.Name),
                    slide: onSlideEvent,
                    start: onStartSlide,
                    stop: onEndSlide,
                    animate: true,
                    change: (event, ui) => {
                        // find value in values list...
                        angular.forEach(scope.model.PossibleValues, (val) => {
                            if (parseInt(val.Name) === ui.value) {
                                scope.$apply(() => {
                                    scope.model.Value = val;
                                });
                            }
                        });

                        onChangeEvent(event, ui);
                    }
                });
            });
        }
    };
});

【讨论】:

    【解决方案2】:

    我希望您的 $scope.$watch 逻辑会让您失望。最简单的方法可能是使用 $watchCollection 监视整个问题数组:

    $scope.$watchCollection(questions, function(newValue, oldValue) {
        for(index=0; index<newValue.length; index++) {
            if (newValue[index] !== oldValue[index]) {
                console.log("Question " + index + " updated to: " + newValue[index]);
            }
        }
    });
    

    否则,您可能会为 ng-repeat 循环中的每个项目创建一个单独的控制器,并在那里有一个处理更改的手表。我也不喜欢这个解决方案,因为它有点啰嗦。首先,一个处理问题的新控制器:

    app.controller('QuestionCtrl', function($scope) {
        $scope.$watch('question', function(newValue, oldValue) {
                if (newVal === oldVal) {
                    return;
                }
    
                console.log(newVal);
        }
    });
    

    稍微修改您的视图以包含对新 QuestionCtrl 的引用:

    <div data-ng-repeat="question in Questions" ng-controller='QuestionCtrl'>
        <h4>{{question.QuestionText}}</h4>
        <p>{{question.RangeMinText}}</p>
        <questionslider ng-model="question"></questionslider>        
        <p>{{question.RangeMaxText}}</p>
    </div>
    

    This article 提供了有关使用带有 ng-repeat 的控制器的更多信息。

    我希望其中一个对您有所帮助。

    【讨论】:

    • 我根据上面的这些建议更新了问题。
    猜你喜欢
    • 2018-04-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-05-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多