Як зробити двосторонню фільтрацію в AngularJS?


124

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

У KnockoutJS я міг створити обчислюване властивість читання / записування, що дозволило мені вказати пару функцій, ту, яка викликається для отримання значення властивості, і та, яка викликається, коли властивість задана. Це дозволило мені реалізувати, наприклад, відомості про культуру - дозволяючи користувачеві набрати "1,24 долара" та проаналізувати це на поплавок у 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/ )

Елемент введення дуже заплутався, коли користувач починає вводити текст. Я покращив його, розділивши властивість на дві властивості, одну для нерозбірливого значення та одну для розбору значення:

$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

Виявляється, є дуже елегантне рішення цього, але воно недостатньо задокументоване.

Значення моделювання форматування для відображення можуть оброблятися |оператором і кутовим formatter. Виявляється, що ngModel має не лише список форматів, а й список парсерів.

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

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

2. Створіть у своєму кутовому модулі директиву, яка буде застосована до того ж елемента, що залежить від 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 для контролера Модель також має короткий опис і огляд інших доступних методів.


Чи є якась причина, що ви використовували "ngModel" як ім'я для четвертого параметраte у своїй функції зв’язку? Це не просто загальний контролер для директиви, який в основному не має нічого спільного з атрибутом ngModel? (Тут все ще навчаюсь кутовим, щоб я міг абсолютно помилятися.)
Дрю Міллер

7
Через "вимагаю: 'ngModel" "четвертим параметром функції зв'язування буде контролер директиви ngModel - тобто контролер foo.bar, який є примірником ngModelController . Ви можете назвати 4-й параметр, що завгодно. (Я б назвав це ngModelCtrl.)
Марк Райчко

8
Ця методика задокументована на docs.angularjs.org/guide/forms , у розділі Спеціальна перевірка.
Nikhil Dabas

1
@Mark Rajcok у наведеній скрипці, натискаючи кнопку "Завантажити дані" - всі малі регістри, я очікував, що значення моделі буде ВСІМ КАПС, але значення моделі було малим. Не могли б ви pls. поясніть, чому і як зробити модель завжди в КАПС
Rajkamal Subramanian

1
@rajkamal, оскільки loadData2 () модифікується $scopeбезпосередньо, саме для цього буде встановлена ​​модель ..., поки користувач не взаємодіє з текстовим полем. Після цього будь-який аналізатор може впливати на значення моделі. На додаток до аналізатора, ви можете додати годинник $ до свого контролера для перетворення значення моделі.
Марк Райкок
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.