Как сделать двухстороннюю фильтрацию в AngularJS?

124

Одна из интересных вещей, которую может сделать AngularJS, - это применить фильтр к определенному выражению привязки данных, что является удобным способом применения, например, валюты, зависящей от языка и региональных параметров, или форматирования даты свойств модели. Также хорошо иметь вычисляемые свойства в области видимости. Проблема в том, что ни одна из этих функций не работает со сценариями двусторонней привязки данных - только односторонняя привязка данных от области видимости к представлению. Это кажется вопиющим упущением в отличной библиотеке - или я чего-то упускаю?

В KnockoutJS я мог бы создать вычисляемое свойство чтения / записи, которое позволило мне указать пару функций: одну, которая вызывается для получения значения свойства, и другую, которая вызывается при установке свойства. Это позволило мне реализовать, например, ввод с учетом языка и региональных параметров, позволив пользователю ввести «$ 1,24» и преобразовать его в число с плавающей запятой в ViewModel, и внести изменения в ViewModel, отраженные во вводе.

Самое близкое, что я мог найти похожее на это, - это использование $scope.$watch(propertyName, functionOrNGExpression);This, которое позволяет мне вызывать функцию при $scopeизменении свойства . Но это не решает, например, проблему ввода с учетом культуры. Обратите внимание на проблемы, когда я пытаюсь изменить $watchedсвойство в самом $watchметоде:

$scope.$watch("property", function (newValue, oldValue) {
    $scope.outputMessage = "oldValue: " + oldValue + " newValue: " + newValue;
    $scope.property = Globalize.parseFloat(newValue);
});

( http://jsfiddle.net/gyZH8/2/ )

Когда пользователь начинает печатать, элемент ввода очень сбивается с толку. Я улучшил его, разделив свойство на два свойства, одно для неанализируемого значения, а второе для проанализированного значения:

$scope.visibleProperty= 0.0;
$scope.hiddenProperty = 0.0;
$scope.$watch("visibleProperty", function (newValue, oldValue) {
    $scope.outputMessage = "oldValue: " + oldValue + " newValue: " + newValue;
    $scope.hiddenProperty = Globalize.parseFloat(newValue);
});

( http://jsfiddle.net/XkPNv/1/ )

Это было улучшением по сравнению с первой версией, но оно немного более подробное, и обратите внимание, что все еще существует проблема, parsedValueсвязанная с изменением свойства области видимости (введите что-нибудь во втором вводе, которое изменяет parsedValueнепосредственно. Обратите внимание, что верхний ввод не Обновить). Это может произойти из-за действия контроллера или из-за загрузки данных из службы данных.

Есть ли более простой способ реализовать этот сценарий с помощью AngularJS? Мне не хватает некоторых функций в документации?

Джереми Белл
источник

Ответы:

231

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

Форматирование значений модели для отображения может выполняться |оператором и angular formatter. Оказывается, ngModel имеет не только список средств форматирования, но и список синтаксических анализаторов.

1. Используйте ng-modelдля создания двусторонней привязки данных.

<input type="text" ng-model="foo.bar"></input>

2. Создайте директиву в своем модуле angular, которая будет применяться к тому же элементу и зависит от ngModelконтроллера.

module.directive('lowercase', function() {
    return {
        restrict: 'A',
        require: 'ngModel',
        link: function(scope, element, attr, ngModel) {
            ...
        }
    };
});

3. В linkметоде добавьте свои настраиваемые преобразователи в ngModelконтроллер.

function fromUser(text) {
    return (text || '').toUpperCase();
}

function toUser(text) {
    return (text || '').toLowerCase();
}
ngModel.$parsers.push(fromUser);
ngModel.$formatters.push(toUser);

4. Добавьте новую директиву к тому же элементу, у которого уже есть ngModel

<input type="text" lowercase ng-model="foo.bar"></input>

Вот рабочий пример, который преобразует текст в нижний регистр в модели inputи обратно в верхний регистр.

Документация API для контроллера Модель также имеет краткое описание и обзор других доступных методов.

phaas
источник
Есть ли причина, по которой вы использовали «ngModel» в качестве имени четвертого параметра в вашей функции связывания? Разве это не общий контроллер для директивы, который в основном не имеет ничего общего с атрибутом ngModel? (Все еще изучаю угловой здесь, поэтому могу быть совершенно неправ.)
Дрю Миллер
7
Из-за «require: 'ngModel'» 4-м параметром связывающей функции будет контроллер директивы ngModel, то есть контроллер foo.bar, который является экземпляром ngModelController . Вы можете назвать 4-й параметр как хотите. (Я бы назвал это ngModelCtrl.)
Марк Райкок
8
Этот метод задокументирован на docs.angularjs.org/guide/forms в разделе Custom Validation.
Nikhil Dabas
1
@Mark Rajcok в предоставленной скрипке, нажимая Загрузить данные - все строчные буквы, я ожидал, что значение модели будет ВСЕМИ ЗАГЛАВНЫМИ буквами, но значение модели было маленьким. Не могли бы вы, пожалуйста. объясните, почему и как сделать так, чтобы модель всегда ЗАГЛАВНЫМ
Rajkamal Subramanian
1
@rajkamal, поскольку loadData2 () модифицируется $scopeнапрямую, модель будет настроена на это ... до тех пор, пока пользователь не взаимодействует с текстовым полем . В этот момент любые парсеры могут повлиять на значение модели. В дополнение к синтаксическому анализатору вы можете добавить к контроллеру $ watch для преобразования значения модели.
Марк Райкок,