AngularJs — привязать одну ng-модель к директиве с двумя входами

Как создать директиву range, которая привязывается к одному ng-model и выводит два поля input с использованием filter (уже созданного). По сути, у меня работает одно направление от модели к входным данным, а другое от входных данных к модели — нет. Как этого добиться?

У меня есть этот HTML:

<div tu-range ng-model="arbitraymodel" />

И модель:

var arbitrarymodel = "10/22";

Примечание; Я создал фильтр для разделения этих двух значений:
{{ feature.Value | split:\'/\':0}}

И эта директива:

.directive('tuRange', function($compile) {
    return { 
        restrict: 'A',
        require: 'ngModel',
        scope: {
            feature: '=',
            tudisabled: '=',
            model: '=ngModel' // edited
        },
        template: '<input type="text" '+
               'ng-value="{{ model | split:\'/\':0}}" />'+ // edited to 'model'
             '-<input type="text" '+
               'ng-value="{{ model | split:\'/\':1}}" />', // edited to 'model'
        link: function(scope, element, attributes, ngModel) {

        }
    };
})

person Highmastdon    schedule 05.08.2014    source источник
comment
Вам действительно нужно сделать это со значением 10/22? Почему бы не использовать для этого объект с ключами arbitrary.begin и arbitrary.end? Вы можете объединить оба значения обратно в одно значение, если хотите, с прослушивателем изменений.   -  person ConcurrentHashMap    schedule 05.08.2014
comment
хм, не совсем уверен в этом, но как он реверсирует фильтрацию для двусторонней привязки?   -  person Raphael Müller    schedule 05.08.2014
comment
Я внес небольшое изменение в область действия директивы, добавив model. @ConcurrentHashMap Да, это не самые оптимальные данные, которые я получаю. Но в конечном итоге это должно быть отправлено обратно таким же образом, поэтому, чтобы убедиться, что все происходит в одном месте, я хотел бы сделать это в директиве.   -  person Highmastdon    schedule 05.08.2014


Ответы (1)


Правильный способ (IMO) — создать собственный элемент управления, как описано здесь. .

В качестве упражнения я реализовал это в этой скрипке: http://jsfiddle.net/6cn7y/

Код директивы (вам может потребоваться адаптировать некоторые детали):

app.directive("range", function() {
    var ID=0;

    function constructRangeString(from, to) {
        var result;
        if( !from && !to ) {
            result = null;
        }
        else if( from ) {
            result = from + "/" + (to || "");
        }
        else if( to ) {
            result = "/" + to;
        }
        return result;
    }

    return {
        require: "ngModel",
        restrict: "E",
        replace: true,
        scope: {
            name: "@"
        },
        template:
            '<div ng-form="{{ subFormName }}">' +
                '<input type="text" ng-model="from" class="range-from" />' +
                '<input type="text" ng-model="to" class="range-to" />' +
            '</div>',
        link: function(scope,elem,attrs,ngModel) {
            var re = /([0-9]+)\/([0-9]+)/;

            if( scope.name ) {
                scope.subFormName = scope.name;
            }
            else {
                scope.subFormName = "_range" + ID;
                ID++;
            }

            ngModel.$render = function() {
                var result, from, to;
                result = re.exec(ngModel.$viewValue);
                if( result ) {
                    from = parseInt(result[1]);
                    to = parseInt(result[2]);
                }
                scope.from = from;
                scope.to = to;
            };

            scope.$watch("from", function(newval) {
                var result = constructRangeString(newval, scope.to);
                ngModel.$setViewValue(result);
            });
            scope.$watch("to", function(newval) {
                var result = constructRangeString(scope.from, newval);
                ngModel.$setViewValue(result);
            });
        }
    };
});

И его использование будет:

<range ng-model="ctrl.theRange" name="myRange" required="true"></range>

Я сомневаюсь, что фильтры помогут вам в этом, поскольку они не выполняют двустороннюю привязку.


EDIT: несмотря на то, что это решает проблему, я бы предложил немного другой подход. Я бы определил модель директивы range как объект:

{
    from: ...,
    to:   ...
}

Это означает, что вывод в переменной ctrl.theRange в примере будет таким же объектом, как и выше. Если вам действительно нужен строковый формат "from/to", добавьте парсер/форматтер в конвейеры ngModel, то есть функцию constructRangeString(). Используя синтаксический анализатор/форматер, переменная ctrl.theRange получает желаемый строковый формат, сохраняя при этом код более модульным (функция constructRangeString() является внешней по отношению к директиве) и более параметризованным (модель находится в формате, который легко обрабатывается и преобразуется).

И доказательство концепции: http://jsfiddle.net/W99rX/.

person Nikos Paraskevopoulos    schedule 05.08.2014
comment
Вау, очень красиво сделано. Мне особенно нравится второй способ, который действительно более модульный, а значит, лучше тестируемый. Большое спасибо! - person Highmastdon; 06.08.2014
comment
Несколько комментариев: 1. Если вы заполните «0», модель станет пустой, потому что вы проверяете if(!from && !to) return null;, что приводит к пустой модели. 2. при использовании около 180 диапазонов (потому что это сетка данных) это очень медленно. - person Highmastdon; 06.08.2014
comment
1. Да, я ожидаю несколько шероховатостей. Другим может быть регулярное выражение. И вам может понадобиться unshift() из $formatters вместо pushing - я все время путаюсь с этим. 2. Это интересно (и прискорбно). Одна оптимизация, которую я бы попробовал, - это получить значения представления непосредственно с событиями DOM, а не использовать вложенную форму и ng-models. Это сократит часы, но усложнит код. Еще одна оптимизация — попытаться заменить глубокие часы двумя отдельными часами для "from" и "to". Я не ожидаю, что это сделает много, но вы никогда не знаете. - person Nikos Paraskevopoulos; 06.08.2014
comment
Спасибо за ваш ответ. Я пробовал несколько оптимизаций, в том числе с удалением RexExp, но в итоге это было медленно. Итак, я взял набор, полученный через $http(), изменил значения с помощью value.split('/') (с несколькими проверками) и поместил его в глобальные model.from/.to, и даже сейчас он все еще работает медленно. Так что я думаю, что я должен справиться с этим. Или, может быть, только обновить model, используя ng-blur для обновления значений. Я должен посмотреть, что делать :). Спасибо большое пока :) - person Highmastdon; 06.08.2014
comment
Я все еще пытаюсь разобраться в хитросплетениях директив Angular. Я думал, что для обновления html использовался метод $render. Так почему же вы устанавливаете переменные scope.from и scope.to внутри метода $render вместо того, чтобы делать что-то вроде element.find('input').eq(0).val(from) (и аналогично для to)? - person battmanz; 22.06.2015
comment
Это удобно, потому что шаблон пользовательского элемента управления использует ng-model в своих входных данных. Действительно, здесь не только возможно, но и потенциально более эффективно использовать манипуляции с DOM из-за меньшего количества часов. С другой стороны, это требует больше кода: не только в $render, но и в другую сторону: вы должны прослушивать соответствующие события входных данных и запускать обновления модели. Если вы хотите быть кратким с Angular, вам также нужно соблюдать ng-model-options. - person Nikos Paraskevopoulos; 22.06.2015
comment
Упомянутый выше jsFiddle (jsfiddle.net/W99rX) работает для угловой версии 1.2.x, но я обнаружил, что не могу не заставить его работать в Angular 1.4.x После некоторого чтения документов я изменил строку, которая читает ngModel.$setViewValue(newval); в ngModel.$setViewValue(angular.copy(newval)); и это сработало, как и ожидалось. - person John; 27.10.2015