自定义指令的多个实例,与ngModel混淆

时间:2015-02-09 12:52:59

标签: angularjs angularjs-directive angular-ngmodel

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

我遇到了手表的问题,因为看起来手表总是出现在页面的最后一个问题上。因此,例如,使用问题1上的滑块的10个问题的页面 - 在最后一个问题(问题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);
}

实例化questionliders的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
}

所以问题是,无论我使用slider指令更新哪个问题,它始终是传递给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; }

作为第一个表达式。仍然没有运气。

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

更新 所以我尝试为每个问题使用单独的控制器,在控制器中为每个问题添加一个监视器,奇怪的是即使这是再现相同的情况。它仍然是传递给监视功能的页面上的最后一个问题。

标记

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

它必须与我的自定义指令有关,我是否在页面上有多个实例时覆盖以前的实例?

2 个答案:

答案 0 :(得分:0)

我希望你的$ 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的控制器的更多信息。

我希望其中一个可以帮助你。

答案 1 :(得分:0)

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

因此解决方案是将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);
                    }
                });
            });
        }
    };
});