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

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

В KnockoutJS я мог бы создать вычисляемое свойство чтения/записи, что позволило мне указать пару функций, которые вызывается, чтобы получить значение свойства, и тот, который вызывается, когда свойство задано. Это позволило мне реализовать, например, вход в систему, ориентированный на культуру, - позволяя пользователю вводить "$ 1.24" и анализируя это в float в ViewModel и иметь изменения в ViewModel, отраженные на входе.

Самое близкое, что я мог найти, похоже на использование $scope.$watch(propertyName, functionOrNGExpression);. Это позволяет мне вызвать функцию, вызываемую при изменении свойства в $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/)

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

$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? Не хватает ли некоторых функций в документации?

Ответ 1

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

Форматирование значений модели для отображения может выполняться оператором | и 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 для Контроллера моделей также содержит краткое объяснение и обзор других доступных методов.